feat(dynseal): the server asserts its schema instead of inventing one #11
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/dynseal-postgres"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Stage 3 of reviving dynseal, on top of #10 (merged). Needs spel#901's tables to
actually run, but nothing here applies anything.
The split this closes
Two modules disagreed about who owns dynseal's schema:
dynseal-pattern-publisher.pydynseal_server.pyEach was internally consistent, which is why nothing caught it.
There is now one answer —
dynseal/forge_db.py— holding the connection and thecheck. It reads
information_schemarather than probing each table with aSELECT, because a probe cannot tell no such table from no permission to readit and those want different fixes. It creates nothing.
start-dynseal-server.sh's inlineCREATE TABLEblock goes too. That blockwrote
./data/foundation.dbwhile the server opened a different file besideitself — so it had never initialised the database it claimed to.
The hardcoded paths — why none of this could run
Three modules carried an absolute path to a directory that no longer exists.
forge_config's own rule, at the top of that file, is "no hardcoded defaults";spel_root()is that rule applied — env override, then manifest, like everyother value there.
What is deliberately NOT converted
The part worth arguing with. Three modules keep SQLite:
dynseal-pattern-publisher.pyartifactsdynseal-p2p-sync.pytrust_logactor_manifest_integration.pyactors,trust_logNone of those tables is defined by the dynseal migration.
trust_logexistsin no database here — the forge has
core.trust_ledger, a different shape and adifferent decision.
artifactsexists only asux.artifacts, something elseentirely, which would not resolve unqualified.
So converting them is not a mechanical port; it needs a schema decision about
what dynseal's attestations have to do with the forge's trust ledger. Converting
half of a module — its
supply_chain_logread but not itstrust_logwrite —would move the split rather than close it, which is the mistake this PR undoes.
Each says so at the line where it matters. The publisher's error message is
corrected while it waits: it used to tell you to apply a Postgres migration to
fix a SQLite problem.
Testing
Against the live database, read-only. All three tables are absent today, so the
refusal path is real rather than simulated:
The other two paths were exercised by pointing
REQUIRED_TABLESat tables thatdo exist:
Also: every
dynseal/*.pyparses,bash -n start-dynseal-server.shis clean, andtools/check_schema_registry.pystill reports the registry consistent.Impact Statement
Breaking changes: the server now refuses to start without its schema,
where it previously created one. That is the intended change, and until spel#901
merges it means the server will not start at all. It could not usefully start
before either — it was writing to a SQLite file nothing else read.
Behaviour change:
dynseal_server.pytalks to the forge Postgres databasevia
forge_config.database_url()—DATABASE_URL, then the manifest, then aRuntimeError. No silent local fallback, deliberately: a silent fallback is howit ended up writing somewhere nobody read.
Risk. The two converted query sites are untested against real tables, because
the tables do not exist yet. They are a parameter-style change (
?→%s) and aconnection swap; the SQL is otherwise unchanged. Once spel#901 lands,
./start-dynseal-server.shplus
./test-dynseal-api.shexercises both.What this does not do: give dynseal a caller. It still has none. That is the
next stage, and it is the one that may conclude there isn't an honest one.