@siax/estate (1.0.0)
Installation
@siax:registry=https://git.cloud.siax.io/api/packages/sax3l/npm/npm install @siax/estate@1.0.0"@siax/estate": "1.0.0"About this package
@siax/estate
The committed, re-runnable generator for standard/estate/ — and the read
API that answers "is this repository registered in the estate?".
Why it exists
P001 produced the estate registry with "a one-time authoring tool"
(IMPLEMENTATION_REPORT.md §7) that was never committed. The consequences
were exactly what an uncommitted generator produces:
vibeandsiax-cliwere created on 2026-09-03 and existed for a day without appearing in a single estate document;- nine companion documents still said "79 repos" after the registry said 81;
- every
githubMirrorfield went stale the moment estate-wide push mirroring was switched on, because nothing re-derived them.
A registry that is true once is not a registry. This package makes regeneration a command, and makes drift a failing CI step.
The two halves
READ loadEstateRegistry() / resolveEstateRegistration()
One file read, no network. @siax/write-guard depends on this
(P003) so that "registered in the estate" is an INPUT to the
repository-write gate rather than something a repo asserts about
itself.
WRITE captureSnapshot() -> standard/estate/SIAX_ESTATE_GITEA_SNAPSHOT.json
generateEstateDocuments() -> every other file in standard/estate/
Running it
# 1. Capture live git.siax.io (the ONLY step that touches the network).
GITEA_TOKEN=… node packages/estate/bin/estate-snapshot.mjs
# 2. Regenerate every document from that snapshot. Offline, deterministic.
node packages/estate/bin/generate-estate.mjs
# 3. What CI runs: fails if anything on disk differs from step 2's output.
node packages/estate/bin/generate-estate.mjs --check
--check needs no token, because the snapshot is committed. That splits two
different questions cleanly:
| Question | Where it is answered |
|---|---|
| Do the documents match the snapshot? | --check, in this repo's CI, every PR |
| Does the snapshot still match Gitea? | sax3l/siax-automation's scheduled estate-registry-freshness job — the only place with a token |
The merge rule
Three kinds of field, one rule each. This is the whole design:
- Live-derived (
id,giteaFlags,lastUpdated,githubMirror,gitea.*) — always overwritten from the snapshot. Facts git.siax.io owns. They cannot go stale. - Curated (
tier,category,owner,notes,anomalies,consumers,datastores,secretRefs) — carried forward verbatim. Human judgements no API can re-derive. A repo the previous registry never saw getsUNKNOWN, never a guess. - Derived (
writable,writableReason, every count) — recomputed from the two above by a stated rule, so they cannot disagree with their inputs.
A repo that disappears from Gitea moves to removedSinceLastGeneration
rather than vanishing from the diff.
manifestDeclared mirrors each repo's own siax.repo.v3.json (or the older
siax.repo.json — 27 of 81 repos have only that) and is kept separate
from the curated fields on purpose: when the two disagree that is a finding
in SIAX_ESTATE_DETECTIONS.md, not something the generator quietly
harmonises.
Files this package owns vs. reads
| File | Owner |
|---|---|
SIAX_ESTATE.yaml, SIAX_ESTATE_DETECTIONS.{md,json}, SIAX_*_INVENTORY.md, SIAX_REPOSITORY_OWNERSHIP.md, SIAX_CAPABILITY_OWNERSHIP.md, SIAX_DEPENDENCY_GRAPH.md, SIAX_ARCHIVE_CANDIDATES.md |
generated — do not hand-edit |
SIAX_ESTATE.md |
partly generated — the last-activity and flag columns are rewritten each run; the human "Motivering" prose is never touched |
SIAX_ESTATE_CURATED.yaml |
hand-edited input — read, never written |
SIAX_PRODUCT_FIREWALL.yaml, SIAX_TAXONOMY.yaml, SIAX_CI_SECRET_SCAN_INVENTORY.* |
owned by other passes — read only |
Section contracts (do not rename these headings)
Two generated documents are parsed by other packages, positionally:
SIAX_DEPENDENCY_GRAPH.md→## Full engine consumption tableand## Reverse index …, read bypackages/network-policy/src/parse-dependency-graph.mjs.SIAX_CAPABILITY_OWNERSHIP.md→## Capabilities with exactly one confirmed owner, six columns in a fixed order, read bypackages/doctor/src/lib/capability-ownership-md.mjs(whichsiax doctor datastore-ownershipbuilds its whole capability→owner map from).
Renaming a heading or inserting a column makes those parsers return zero
rows silently. packages/*/test covers both; the first draft of this
renderer broke them, which is why they are written down here.
The YAML module
src/yaml-doc.mjs is a narrow, hand-written reader/writer for exactly the
shape these files use — not a YAML implementation, in the same tradition as
packages/write-guard/src/product-firewall.mjs. Unsupported constructs are
a hard parse ERROR, never a silent skip.
It was validated during development against PyYAML 6.0.1 as an
independent oracle on all four real committed estate YAMLs: parse(real)
matched PyYAML exactly, and PyYAML(emit(parse(real))) matched
PyYAML(real). That oracle found two bugs a self-round-trip test could
never find, because both sides of a self-test share the same mistake:
- quoted date strings emitted bare, so every other YAML consumer read
lastUpdated: 2026-09-02as a date object; - trailing spaces inside a
|block scalar being trimmed on the way back in — content, silently dropped, in a real Gitea push-mirror error.
PyYAML is a development-time check only; the committed package stays zero-dependency.