Two commits, five files, no runtime code — nothing in backend/, frontend/ or alembic/. The schema work already merged in #243; this is the public-availability groundwork around it.
The repo had no LICENSE, which means all rights reserved by default: nobody could legally run or modify it, and "public availability" was a contradiction no amount of install documentation could fix. This was the one hard blocker in milestone 328.
AGPL-3.0, at the operator's explicit choice, for the reason they gave — this should not become something another party runs as a hosted proprietary service. §13 is what makes it the right fit rather than the GPL: for a self-hosted web app, distribution essentially never happens, so the GPL's obligation would never bite. The AGPL reaches the case that actually matters, which is running a modified copy as a service for other people.
LICENSE is the FSF text fetched from gnu.org and verified byte-identical (34,523 bytes, 661 lines, §13 "Remote Network Interaction" present) — not retyped. The README's old "Personal project; use at your own discretion" said nothing legally and is replaced with what the licence actually asks, including the part worth being explicit about: running an unmodified copy for yourself carries no obligation at all.
SECURITY.md names what this software actually holds, because that is what makes a report serious here — live third-party session cookies for accounts with payment methods attached, the extension API key, the multi-user sharing ACL, and arbitrary downloaded media that gets decoded and fed to models. It states the plain-HTTP posture up front so "served over HTTP" and "no HSTS" are understood as the documented design rather than filed as findings.
There is no private disclosure channel yet, so the reporting instruction is: open an issue containing nothing but the fact that a report exists, and wait for a private contact. Awkward on purpose — an issue tracker is public the moment it is written to, and every self-hosted instance stays vulnerable until its operator can update. Worth replacing with a real address; that decision publishes one, so it is the operator's.
CONTRIBUTING.md records the two things that actually catch people: ruff's order-by-type import sorting, and that a model change and its migration belong in the same commit. The second is not style — the models and the chain silently diverged for a long time (#3275), and --autogenerate was unsafe as a result.
Pre-publication scan, since the repo is about to get attention: .env.example is placeholders only (changeme_*), .env is gitignored with an !.env.example exception, no credential-shaped literals are committed, no private IPs, no operator home paths. The only hostnames are the project's own forge, public by design.
docker-compose.yml pinned :dev on all five app services. The README documents docker compose -f docker-compose.yml up -d as production, and -f means use only this file — skipping the override and its build: directives, so Compose pulled image:, and image: was the rolling development channel. The documented way to install this product shipped development builds.
It went unnoticed for a structural reason rather than a careless one: nobody who works on the project takes that path. The operator deploys from a swarm stack file; contributors get docker-compose.override.yml, whose build: wins over image:. The broken path is reachable only by a stranger following the README — exactly the audience that did not exist until now.
Now :latest, per rule 147 (mainis production). It is also what agent/docker-compose.yml already pinned, so the two stacks now agree rather than this introducing a new convention.
Both paths verified with docker compose config, which merges and prints without starting anything:
dev path — build: present on all five, image: not pulled
-f production — 0 build: directives, five :latest images resolved
Also checked the base file for anything a stranger could not satisfy: no host-absolute volume paths, no operator-specific port bindings, no device mappings. The tag was the only defect in the consumer path.
Verification
ci.yml run 5109 on bc4eba6: lint, extension-version, backend tests, frontend build, integration — all five green.
Two commits, five files, **no runtime code** — nothing in `backend/`, `frontend/` or `alembic/`. The schema work already merged in #243; this is the public-availability groundwork around it.
## AGPL-3.0 (#3269)
The repo had **no LICENSE**, which means all rights reserved by default: nobody could legally run or modify it, and "public availability" was a contradiction no amount of install documentation could fix. This was the one hard blocker in milestone 328.
AGPL-3.0, at the operator's explicit choice, for the reason they gave — this should not become something another party runs as a hosted proprietary service. §13 is what makes it the right fit rather than the GPL: for a self-hosted web app, *distribution* essentially never happens, so the GPL's obligation would never bite. The AGPL reaches the case that actually matters, which is running a **modified** copy as a service for other people.
`LICENSE` is the FSF text fetched from gnu.org and verified byte-identical (34,523 bytes, 661 lines, §13 "Remote Network Interaction" present) — not retyped. The README's old *"Personal project; use at your own discretion"* said nothing legally and is replaced with what the licence actually asks, including the part worth being explicit about: running an unmodified copy for yourself carries no obligation at all.
`SECURITY.md` names what this software actually holds, because that is what makes a report serious here — live third-party session cookies for accounts with payment methods attached, the extension API key, the multi-user sharing ACL, and arbitrary downloaded media that gets decoded and fed to models. It states the plain-HTTP posture up front so "served over HTTP" and "no HSTS" are understood as the documented design rather than filed as findings.
There is no private disclosure channel yet, so the reporting instruction is: open an issue containing **nothing but the fact that a report exists**, and wait for a private contact. Awkward on purpose — an issue tracker is public the moment it is written to, and every self-hosted instance stays vulnerable until its operator can update. Worth replacing with a real address; that decision publishes one, so it is the operator's.
`CONTRIBUTING.md` records the two things that actually catch people: ruff's `order-by-type` import sorting, and that a model change and its migration belong in the same commit. The second is not style — the models and the chain silently diverged for a long time (#3275), and `--autogenerate` was unsafe as a result.
**Pre-publication scan**, since the repo is about to get attention: `.env.example` is placeholders only (`changeme_*`), `.env` is gitignored with an `!.env.example` exception, no credential-shaped literals are committed, no private IPs, no operator home paths. The only hostnames are the project's own forge, public by design.
## The install path pulled `:dev` (#3270)
`docker-compose.yml` pinned `:dev` on all five app services. The README documents `docker compose -f docker-compose.yml up -d` as production, and `-f` means *use only this file* — skipping the override and its `build:` directives, so Compose pulled `image:`, and `image:` was the rolling development channel. **The documented way to install this product shipped development builds.**
It went unnoticed for a structural reason rather than a careless one: nobody who works on the project takes that path. The operator deploys from a swarm stack file; contributors get `docker-compose.override.yml`, whose `build:` wins over `image:`. The broken path is reachable only by a stranger following the README — exactly the audience that did not exist until now.
Now `:latest`, per rule 147 (`main` **is** production). It is also what `agent/docker-compose.yml` already pinned, so the two stacks now agree rather than this introducing a new convention.
Both paths verified with `docker compose config`, which merges and prints without starting anything:
```
dev path — build: present on all five, image: not pulled
-f production — 0 build: directives, five :latest images resolved
```
Also checked the base file for anything a stranger could not satisfy: no host-absolute volume paths, no operator-specific port bindings, no device mappings. The tag was the only defect in the consumer path.
## Verification
`ci.yml` run 5109 on `bc4eba6`: lint, extension-version, backend tests, frontend build, integration — all five green.
Scribe: #3269, #3270, milestone 328.
The repo had no LICENSE, which meant all rights reserved by default:
nobody could legally run or modify it, and "public availability" was a
contradiction no amount of install documentation could fix. This is the
hard blocker in milestone 328; everything else in it is quality.
AGPL-3.0, at the operator's explicit choice, for the reason the operator
gave: this should not become something another party runs as a hosted
proprietary service. Section 13 is what makes it fit — for a self-hosted
web app, distribution otherwise never happens, so the GPL's obligation
would never actually bite. AGPL reaches the case that matters here:
running a MODIFIED copy as a service for others.
LICENSE is the FSF text fetched from gnu.org and verified byte-identical
(34,523 bytes, 661 lines, §13 "Remote Network Interaction" present), not
retyped. README's old "Personal project; use at your own discretion" said
nothing legally and is replaced with what the licence actually asks —
including the part worth being clear about, that running an unmodified
copy for yourself carries no obligation whatsoever.
SECURITY.md names what this software actually holds, because that is what
makes a report serious here: live third-party session cookies for
accounts with payment methods attached, the extension API key, the
multi-user sharing ACL, and arbitrary downloaded media that gets decoded
and fed to models. It also states the plain-HTTP posture up front, so
"served over HTTP" and "no HSTS" are understood as the documented design
rather than filed as findings.
There is no private disclosure channel yet, so the reporting instruction
is to open an issue containing NOTHING but the fact that a report exists,
and wait for a private contact. Awkward on purpose: an issue tracker is
public the moment it is written to, and every self-hosted instance stays
vulnerable until its operator can update. Worth replacing with a real
contact address — that decision is the operator's, since it publishes one.
CONTRIBUTING records the two things that actually catch people: ruff's
order-by-type import sorting, and that a model change and its migration
belong in the same commit. The second is not style — the models and the
chain silently diverged for a long time (#3275) and autogenerate was
unsafe as a result.
Pre-publication scan, since the repo is about to get attention:
.env.example is placeholders only (`changeme_*`), `.env` is gitignored
with an `!.env.example` exception, no credential-shaped literals are
committed, and there are no private IPs or operator home paths. The only
hostnames are the project's own forge, which is public by design.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QHszn9H8VBvx5Ke8x1hvw
docker-compose.yml pinned :dev on all five app services — web, worker,
scheduler, maintenance-long, ml-worker. The README documents
`docker compose -f docker-compose.yml up -d` as the production path, and
-f means "use only this file", skipping the override and its build:
directives. So Compose pulled image:, and image: was the rolling
development channel. The documented way to install this product shipped
development builds.
It went unnoticed for a structural reason rather than a careless one:
nobody who works on the project takes that path. The operator deploys
from a swarm stack file; contributors get docker-compose.override.yml,
which sets build: for all five services, and build: wins over image:. The
broken path is reachable only by a stranger following the README — which
is exactly the audience that did not exist until now.
:latest, per rule 147: main IS production. It is also what the agent
stack (agent/docker-compose.yml) already pinned, so this makes the two
stacks agree rather than introducing a new convention.
Both paths verified with `docker compose config`, which merges and prints
without starting anything:
dev path — build: present on all five, image: not pulled
-f production — 0 build: directives, five :latest images resolved
Also checked the base file for anything a stranger could not satisfy:
no host-absolute volume paths, no operator-specific port bindings, no
device mappings. The tag was the only defect in the consumer path.
The comment on web.image is deliberately long (rule 32). A line reading
:latest inside a file a developer is debugging with is exactly the line
someone flips back to :dev to test something and then commits, and the
consequence — strangers silently installing bleeding edge — is invisible
to everyone who works here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QHszn9H8VBvx5Ke8x1hvw
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two commits, five files, no runtime code — nothing in
backend/,frontend/oralembic/. The schema work already merged in #243; this is the public-availability groundwork around it.AGPL-3.0 (#3269)
The repo had no LICENSE, which means all rights reserved by default: nobody could legally run or modify it, and "public availability" was a contradiction no amount of install documentation could fix. This was the one hard blocker in milestone 328.
AGPL-3.0, at the operator's explicit choice, for the reason they gave — this should not become something another party runs as a hosted proprietary service. §13 is what makes it the right fit rather than the GPL: for a self-hosted web app, distribution essentially never happens, so the GPL's obligation would never bite. The AGPL reaches the case that actually matters, which is running a modified copy as a service for other people.
LICENSEis the FSF text fetched from gnu.org and verified byte-identical (34,523 bytes, 661 lines, §13 "Remote Network Interaction" present) — not retyped. The README's old "Personal project; use at your own discretion" said nothing legally and is replaced with what the licence actually asks, including the part worth being explicit about: running an unmodified copy for yourself carries no obligation at all.SECURITY.mdnames what this software actually holds, because that is what makes a report serious here — live third-party session cookies for accounts with payment methods attached, the extension API key, the multi-user sharing ACL, and arbitrary downloaded media that gets decoded and fed to models. It states the plain-HTTP posture up front so "served over HTTP" and "no HSTS" are understood as the documented design rather than filed as findings.There is no private disclosure channel yet, so the reporting instruction is: open an issue containing nothing but the fact that a report exists, and wait for a private contact. Awkward on purpose — an issue tracker is public the moment it is written to, and every self-hosted instance stays vulnerable until its operator can update. Worth replacing with a real address; that decision publishes one, so it is the operator's.
CONTRIBUTING.mdrecords the two things that actually catch people: ruff'sorder-by-typeimport sorting, and that a model change and its migration belong in the same commit. The second is not style — the models and the chain silently diverged for a long time (#3275), and--autogeneratewas unsafe as a result.Pre-publication scan, since the repo is about to get attention:
.env.exampleis placeholders only (changeme_*),.envis gitignored with an!.env.exampleexception, no credential-shaped literals are committed, no private IPs, no operator home paths. The only hostnames are the project's own forge, public by design.The install path pulled
:dev(#3270)docker-compose.ymlpinned:devon all five app services. The README documentsdocker compose -f docker-compose.yml up -das production, and-fmeans use only this file — skipping the override and itsbuild:directives, so Compose pulledimage:, andimage:was the rolling development channel. The documented way to install this product shipped development builds.It went unnoticed for a structural reason rather than a careless one: nobody who works on the project takes that path. The operator deploys from a swarm stack file; contributors get
docker-compose.override.yml, whosebuild:wins overimage:. The broken path is reachable only by a stranger following the README — exactly the audience that did not exist until now.Now
:latest, per rule 147 (mainis production). It is also whatagent/docker-compose.ymlalready pinned, so the two stacks now agree rather than this introducing a new convention.Both paths verified with
docker compose config, which merges and prints without starting anything:Also checked the base file for anything a stranger could not satisfy: no host-absolute volume paths, no operator-specific port bindings, no device mappings. The tag was the only defect in the consumer path.
Verification
ci.ymlrun 5109 onbc4eba6: lint, extension-version, backend tests, frontend build, integration — all five green.Scribe: #3269, #3270, milestone 328.