~bigbes/sr-ht-spec

ref: 3cb1c03d8078d5748cc13a2e9bd7ba7d078e1b37 sr-ht-spec/docs d---------
bd5b615a — Eugene Blikh 2 days ago
docs: drop a stray closing tag from ci.md

An XML tag from some tool's output that was written into the file and
committed with it. Predates this branch — it is in ef7bddf too.
6d7dbdb5 — Eugene Blikh 2 days ago
ci: publish this build's own coverage and benchmarks

Serving a revision is two passes over the whole corpus — FromDocuments builds
the archive, LinkPass renders every document to fill the link graph — and a
proposal view is a third over two whole revisions. All three grow with the
space rather than with the request, and this repository had no Benchmark at
all; doc/ and prosediff/ now measure them over a synthetic 208-document space
and a 24-chapter specification carrying one of every change kind.

The manifest gains a coverage and a bench task, both after publish so a
rejected upload cannot cost a shipped apk, and both guarded: the profile must
be non-empty, the benchmark names must be in the file (an empty benchfmt body
uploads and reports success), and a build handed no token says so and exits 0
with the artifact still attached.
ef7bddf8 — Eugene Blikh 3 days ago
ci: publish the apk into artifacts.sr.ht as well

The abuild output goes on to the S3 bucket phoebe re-indexes, and now also to
the ~bigbes/main channel of artifacts.sr.ht, which indexes and signs it in the
same request. One PUT per file with the shared working token; a 409 says the
job ran twice and is a warning rather than a failure.
cb60d4f7 — Eugene Blikh 9 days ago
ci: run the suites against a real Postgres on the builder

93 test files across 16 packages had never run on builds.sr.ht. The manifest
brings up a Postgres in the VM, exports SPECSRHT_TEST_PG and runs gofmt, go vet
and `make test` before abuild.

The DSN guard is the point of it. db/db_test.go, service/fixture_test.go and
cmd/specsrht-migrate/main_test.go all gate on that variable, so an unset DSN
does not fail anything — it skips 67 tests (counted, with and against a local
Postgres) and leaves the build green over the whole persistence layer, the
schema/migrations agreement check included. options="!check" in the APKBUILD
means this task is the only place the suites run at all, so the guard is what
makes that line true.

TEST_TIMEOUT lands with it rather than after the first goroutine dump: the
toolchain's default is a silent ten minutes.
8255ff90 — Eugene Blikh 9 days ago
ci: export the version instead of sed-ing a tracked APKBUILD

The version reaches abuild as $PKGVER now, and the tracked APKBUILD is never
rewritten: Go reads vcs.modified from `git status --porcelain`, so a sed of a
tracked file stamps every packaged binary dirty for the whole abuild run.

The export alone would not have fixed it. .gitignore covered the intermediate
web/static/main.css and not the content-hashed main.min.<sha>.css that `make
css` renames it to — the file that survives, and that build() writes BEFORE go
build — so every packaging run compiled with an untracked file in the tree.
/src/, /pkg/ and /tmp/ are the three directories abuild works in inside this
checkout, GOTMPDIR among them.

Also: the cacher bootstrap splits into install + init so the installer's PATH
export is sourced by the next task, and goes through install.sh for its
checksum verification; scss collapses into the --exec miss-and-seed form;
cache_restore takes --optional plus the half-restored module cache repair;
cache_save moves after publish and drops the exists-guards; publish gates on a
readable ~/.apk-ci.env; tags build.

package() stages what build() produced (make install-files) instead of
recompiling both binaries in a fresh fakeroot process with no cache pins, and
stops installing static assets that //go:embed already put in the binary. The
`[ -d ./cmd/... ]` skip guards are gone: a deleted cmd/ was a green build that
packaged nothing.

The reasoning lives in docs/ci.md, because the manifest is a varchar(16384)
and a submission over the cap gets no CI at all.
a4d8cc52 — Eugene Blikh 9 days ago
authn: remove the local agent-token plane

The agent_token table and everything that minted, verified, listed or revoked
it. Agent credentials are tokens.sr.ht working tokens now: signed, expiring,
owned by a meta.sr.ht account and carrying grants, verified locally by
sr-ht-ecore's bearer package. One door, and nothing behind it — a credential the
instance plane refuses is refused, where it used to be offered to a second store
that might say yes.

DEPLOY GATE: do not deploy this until every agent configured with the shared
secret holds a tokens.sr.ht token with spec:propose (spec:read to read).
Migration 0005 drops the table, migrate-on-upgrade runs it on deploy, and
deploying early locks out every agent at once — including the SSH push path.

hooks/ no longer reads agent_token directly. The push path goes through the same
authn.Resolver the HTTP surfaces use and demands spec:propose, because a push by
an agent is a proposal by another transport. The refs rule and the provenance
requirement are untouched on both counts: a universal grant is still only an
agent to the receive path, and X-Agent / X-Agent-Session are still mandatory on
every agent write.

bearer.ErrNotOurs is now a permanent refusal. A meta.sr.ht PAT used to fall
through to the local store and miss there; with no store to fall through to it
earns a 401 rather than the 503 an unclassified error would.

[tokens.sr.ht] origin becomes a required config key: with no issuer there is no
credential to check, so the daemon fails startup instead of serving reads and
refusing every agent write one request at a time.

/tokens redirects to the daemon that issues (tokens SPEC ch. 7).
0879325b — bigbes 26 days ago
docs: the empty-space-list polarity is retracted, not just documented

Search queries now take the filter type with three explicit states, so an
empty set selects nothing everywhere and the corpus-wide case is spelled
EverythingFilter. The document previously said an empty search space list
means every space, which the fix deliberately reverses.

Also records that introspection is gated along with the rest of the read
plane, and that nothing bounds GraphQL query complexity because the
service authenticates on its own router rather than core-go's.
cac9fe1f — bigbes 27 days ago
docs: three receive hooks, and the read plane's rev guard

The two-hook split was unimplementable. Push options reach pre-receive and
post-receive only, so the skip-validation escape hatch was unreachable
where the design put it; and pre-receive cannot read the pushed objects at
all, because they sit in the quarantine until after it runs. update is
therefore the earliest hook that can validate, which the design relied on
without saying why. Work splits across three hooks correlated by
repository and receive-pack pid.

Records that per-ref rejection is not atomic: a push of main plus a
proposal branch, with main rejected, still lands the proposal.

Settles the read plane's audience, which was anonymous-capable in one
section and leak-free in the verification checklist. It is fail-closed.

Documents the rev guard: reads take the approved head or a full object
name, never a ref, because rev=proposals/42 would otherwise serve
unreviewed text as approved through the read plane itself.
c833182e — bigbes 27 days ago
docs: add the project schema and record the filter-polarity trap

Projects are no longer deferred, so the schema section carries them.

Records why the meta-project is an implicit filter rather than a row: a
stored +everything needs a sync job on every space creation, whose only
failure mode is silently omitting a space, and a corpus-wide view must not
be renameable or deletable.

Flags the polarity hazard between the two layers. An empty project selects
nothing while an empty search space list means every space, so passing a
project's members straight into a query turns an empty project into the
whole corpus. The durable fix is for queries to take the filter type
rather than a bare slice, which is deferred while the read surfaces are
mid-build against the current signature.
54fac8dd — bigbes 27 days ago
docs: resolve bilingual search with per-line routing

Per-document language routing, the option this document suggested, was
implemented and measured as insufficient: a dominantly Russian spec
quoting English verbatim matches attachments but not attachment. A single
analyzer does not break the other language, it silently stops stemming it,
which is why the flaw survives casual testing.

Per-line routing into ru and en title/body fields, queried across all
four, finds both halves. Dual-writing was rejected despite also fixing
stemming, because it biases ranking toward mixed documents by summing two
field scores. The threshold is 35 percent Cyrillic rather than 50, since
Russian technical prose here routinely runs a third Latin.

Also records that filters must be exact-match — warren filtered on an
analyzed field, which at our scale means a filter for one space can return
another's documents, and a project is defined as a space filter — and
corrects post-receive's unit of freshness to a space at a revision.
0780b4cc — bigbes 27 days ago
docs: guard the reconciler against eating live proposals

Implementing the corrected repair table showed it destroys data as
written, in two ways.

Row-first ordering is forced by the schema, so every proposal passes
through the exact state the table says to delete. An ungraced reconciler
on a timer would delete an agent's work at random and never once in a
test. A row younger than the grace window is in flight, not abandoned.

A proposal branch is cut at the approved head, so its tip is trivially an
ancestor of that head until the agent's first commit. The plain ancestry
rule marks a contentless proposal merged, and open to merged is terminal.
Comparing the tip against the recorded base distinguishes never-written
from actually-merged.

Also records the within-pass ordering that makes the grace window
sufficient, that marking merged must invent approval and merged_rev and
why policy is the safe lie, the two states left unrepaired, and that
spaces are created repository-first while proposals are created row-first.
2b11f1ae — bigbes 27 days ago
docs: the design is no longer unimplemented

The header still claimed nothing was built. Six packages and one binary
are committed and green, and the document has been corrected against them
six times.
5d3d9441 — bigbes 27 days ago
docs: pin URL grammar, document addressing, and the attachment gap

The read plane specified both that /~user/space/path.md renders and that
.md returns raw source, which cannot both hold for one URL. Extensions are
format selectors, so a document's own address carries none.

Adds the addressing rule for documents whose id is absent or duplicated,
which skip-validation makes reachable on the approved branch. A duplicated
id resolves to neither document: picking one silently would aim links and
search results at a document nobody chose, with no visible sign.

Records that cross-space id resolution cannot live in the read layer,
since an archive is one space at one revision; the global hop needs the
registry and therefore belongs in service/.

Promotes attachments from a hypothetical to a measured gap: the git layer
walks and accepts .md only, so embeds have nothing to resolve against.
1f4a8eda — bigbes 27 days ago
docs: record the Phase 0 verdict and its Phase 4 requirement

The gate passed on real data: rewrapping this document from 80 to 58
columns without changing a word gives 1563 changed lines under a line
differ and zero under the prose differ.

The verdict carries a requirement rather than just a pass. Thirteen
percent of real prose modifications shred into interleaved fragments
because those paragraphs genuinely were rewritten sentence by sentence,
so it cannot be fixed in the differ. Every such block scores similarity
at or below 0.73, so the review UI must switch to a two-column old/new
view below ~0.75; building only the inline renderer would make one review
in eight unreadable.

Also records that the differ already emits the comment-anchor tuple, with
heading path excluded from the hash so renaming a section does not orphan
every comment beneath it.
db7d6c0f — bigbes 27 days ago
docs: correct the merge model against implementation

Resolves a contradiction: the Space section called policy changes
reviewable, but the merge is keyed by document ID and .spec.yml has none,
so a proposal could never express one. Policy joins deletion and rename as
human-push-only, which costs nothing with a single reviewer.

Corrects the claimed cost of ID-keyed staleness. It needs an ID-to-path
index over both whole trees, not one parse of the changed set; free at our
volume, two full-tree reads at ten thousand documents.

Adds the three staleness cases the two-line rule omitted, each of which is
silent corruption if unhandled, and records that an already-merged
proposal needs an ancestry check because the staleness rule reports a
confusing 409 instead.

Records that malformed documents on the approved branch are tolerated
rather than fatal, since skip-validation guarantees they can exist and
failing the index build would turn one typo into a space-wide outage.
d148a661 — bigbes 27 days ago
docs: fix an unimplementable crash repair

The repair table claimed the reconciler recreates a proposal row from an
orphan ref. It cannot: the id is a Postgres serial, and title, rationale,
base_rev, agent and agent_session exist nowhere in a ref, since trailers
live on commits and title lives nowhere in git.

Splits the truth rule instead. Refs stay authoritative for merged-ness;
the row is authoritative for existence and metadata. Row-first ordering is
already forced by deriving the branch name from the serial, and it is also
the safe order, so the unrecoverable case is designed out rather than
repaired. Orphan refs and contentless rows are deleted, which is safe
because an agent still holds the content and can re-propose.

Also records the schema-enforced invariants, notably that merged implies a
non-null approval so policy merges cannot be laundered as human ones.
7c827866 — bigbes 27 days ago
docs: align provenance section with the one-token decision

The section still described per-space tokens with reader/proposer/writer
roles, which the decisions table and the authorization section had already
retracted in favour of one token plus mandatory provenance.

Also records what implementation had to settle: the agent mailbox derives
from our own origin rather than the bare cookie domain, X-Agent-Base is
mandatory and must be an object name, identity travels as X-Agent and
X-Agent-Session with the base coming only from If-Match, a non-owner human
resolves to anonymous, and cookie expiry is not enforceable so network-key
rotation is the only logout-everywhere lever.
ba61af74 — bigbes 27 days ago
docs: correct the core-go dependency pin

The doc repeated dolt.sr.ht's README claim that core-go is wired via a
replace directive to the instance fork. Neither sibling's go.mod contains
one; both require sourcecraft.dev/bigbes/sr-ht-core directly at
dd418a200152, which is the module path the fork declares from that commit
onward. A replace against the older c2c2f38 is rejected outright, since
its go.mod still declared git.sr.ht/~sircmpwn/core-go.
a7336236 — bigbes 27 days ago
docs: add repo layout, schema, implementation plan and verification

Extends the design with the sections the sibling services carry: package
tree with a strict downward dependency rule, the Postgres schema, a
wave-based implementation plan, an end-to-end verification checklist and
the open risks.

Work is dispatched per the parallel-implementer convention: foundation
commit carries every external dependency and the core package, then
siblings importing it write disjoint directories in parallel. Phase 0
runs the prose-diff spike first because it is the assumption with no
fallback.

Projects and comments are deliberately absent from the schema: a project
is a saved filter that is speculative before there are spaces to filter,
and comment anchoring should be settled against a built review UI rather
than committed to a schema first.
12e043e2 — bigbes 27 days ago
docs: settle the seven questions raised by review

- Read contract keeps pinned ?rev= and the approved/draft split. The
  review UI needs blob->render at arbitrary revs regardless, so pinning is
  nearly free and makes X-Agent-Base auditable.
- Materialized checkout dropped. One read path over git objects for
  approved head, pinned revs and proposal branches alike, which removes
  atomic swap, rev stamps, a cache directory and two crash-repair rows.
  Cost is bounded: vault.Scan touches the filesystem twice and
  vault.FromPages already accepts pre-loaded pages.
- Volume is tens of documents a day, so warren's batch index rebuild is
  absorbed unchanged and incremental indexing is explicitly not built.
- One agent token plus mandatory provenance. The refs rule is the boundary
  that bounds damage; per-space scoping defers to a column and a filter.
- Human pushes are validated too, with a skip-validation push option. The
  risk is a typo corrupting the global ID registry, not malice.
- External corpora left unspecified; global IDs are the only forward
  compatibility needed.
- Name confirmed as spec.sr.ht.
Next