oficina / home Public

Keep historical additive ledgers compatible with serving startup

Paul Campbell pushed by paul@keymail.dev c1f26bbd3f39be835d342bc9e1b3b3be438f631b parent 946a24a
3 files changed, +43 −28
  • implementation/operator-command/proposal.json +24 −12
  • internal/home/operator_schema_linux_test.go +19 −0
  • internal/home/runtime.go +0 −16

CI passed — run details

diff --git a/implementation/operator-command/proposal.json b/implementation/operator-command/proposal.json
index cd1fdda..c3228c3 100644
--- a/implementation/operator-command/proposal.json
+++ b/implementation/operator-command/proposal.json
@@ -1,14 +1,26 @@
{
- "status":"Implemented for review; no deploy, merge or live invocation",
- "parents":["startup6289db0","binding790208e","seed898fe219","opened database4770bc6"],
- "command":"home operator-v1 --db <existing absolute path>, dispatched before framework serving startup; fixed exit0/2/3; panic maps to3 without printing",
- "material":"Closed HOME_OPERATOR_MATERIAL JSON (or existing mutually exclusive _BASE64), maximum16384 bytes. Required version1, role matching HOME_ROLE and pinned command scope, independently provisioned nonzero canonical32-byte std-base64 mac_key. Credential additionally requires owner_seed_key family keypair. Directory additionally requires owner_seed_public canonical rawurl P256 public, provisioning_key family keypair, 1-8 roster_keys with exact derived IDs. Unknown, missing, duplicate and cross-role fields refuse.",
- "reuse":"Existing HOME_DEK, HOME_PEPPER credential only, assertion key/public-key map, directory roster runtime and shared family decoder. No SMTP, sign-in session, preview-origin loading, key generation or migration on command path. Existing NewOperatorPersonResolver and compiled canonical directory policy unchanged.",
- "key_separation":"MAC distinct from DEK and credential pepper. Pairwise public-key separation across authorizer/owner/provisioner/browser/roster roles before opening DB. Current roster serving key must be in provisioning roster-key set.",
- "binding":"Protected role/origin and signing-key separation checked before private startup/signature/database binding. Database exposed only to existing engine built on actual verified connector. Final closure precedes opaque private result release.",
- "schema":"Reuse checker exact schema, ledger checksums and foreign-key/replication shape on the same connection inside BEGIN IMMEDIATE before mutation or replay. Ordinary serving boot applies existing additive role-specific operator migrations and refuses foreign/missing/changed ledger entries. Legacy schema remains unchanged. No command migrations.",
- "inspection":"Preserve existing InspectChallenge and signature context. Explicit directory.inspect action pin permits verified read-only challenge after mutation-wrapper verification fails; both parsers validate distinct signed contexts and complete closed shapes. No unverified fallback. Existing Directory.Inspect then checks schema inside its transaction with query_only=ON. It writes no operator result/state rows. A signed mutation wrapper claiming directory.inspect reaches existing mutation dispatch and refuses.",
- "output":"Require fixed fd1 write-only FIFO before DB open. Opaque result can write only to fd1 after successful checked DB closure, bounded by copied launch deadline and caller cancellation. Nonblocking writes handle backpressure; output/close/expiry errors return3. Partial bytes may exist only in dedicated private pipe and are not a successful result; worker must discard incomplete/nonzero-exit output. No empty-stdout claim after a partial pipe failure.",
- "review_changes_from_fable_draft":"Kept existing inspection wire instead of replacing its protocol; no breaking NewEngine signature. Private schema callback is set only by RunCommand. Used full schema verification rather than ledger-only. Corrected impossible empty-stdout guarantee after partial output failure. Explicitly validate stdout pipe and honor launch deadline during publication.",
- "remaining":["Expanded integrated role/key/schema/refusal/output tests and actual binary dispatch","Fable review of final implementation and config alignment","Full local and exact CI gates","Native platform worker authentication, owner workflow and checkpoint/resume integration","Deployed canary review before merge; concrete approved live maintenance request before provisioning"]
+ "status": "Implemented for review; no deploy, merge or live invocation",
+ "parents": [
+ "startup6289db0",
+ "binding790208e",
+ "seed898fe219",
+ "opened database4770bc6"
+ ],
+ "command": "home operator-v1 --db <existing absolute path>, dispatched before framework serving startup; fixed exit0/2/3; panic maps to3 without printing",
+ "material": "Closed HOME_OPERATOR_MATERIAL JSON (or existing mutually exclusive _BASE64), maximum16384 bytes. Required version1, role matching HOME_ROLE and pinned command scope, independently provisioned nonzero canonical32-byte std-base64 mac_key. Credential additionally requires owner_seed_key family keypair. Directory additionally requires owner_seed_public canonical rawurl P256 public, provisioning_key family keypair, 1-8 roster_keys with exact derived IDs. Unknown, missing, duplicate and cross-role fields refuse.",
+ "reuse": "Existing HOME_DEK, HOME_PEPPER credential only, assertion key/public-key map, directory roster runtime and shared family decoder. No SMTP, sign-in session, preview-origin loading, key generation or migration on command path. Existing NewOperatorPersonResolver and compiled canonical directory policy unchanged.",
+ "key_separation": "MAC distinct from DEK and credential pepper. Pairwise public-key separation across authorizer/owner/provisioner/browser/roster roles before opening DB. Current roster serving key must be in provisioning roster-key set.",
+ "binding": "Protected role/origin and signing-key separation checked before private startup/signature/database binding. Database exposed only to existing engine built on actual verified connector. Final closure precedes opaque private result release.",
+ "schema": "Reuse checker exact schema, ledger checksums and foreign-key/replication shape on the same connection inside BEGIN IMMEDIATE before mutation or replay. Ordinary serving boot applies existing additive role-specific operator migrations through existing migrate.Apply validation; it preserves prior tolerance for unrelated historical ledger entries. Exact set equality is mandatory only for operator/checker, so ordinary serving is not blocked by legacy additive migrations. Legacy role schema unchanged. No command migrations.",
+ "inspection": "Preserve existing InspectChallenge and signature context. Explicit directory.inspect action pin permits verified read-only challenge after mutation-wrapper verification fails; both parsers validate distinct signed contexts and complete closed shapes. No unverified fallback. Existing Directory.Inspect then checks schema inside its transaction with query_only=ON. It writes no operator result/state rows. A signed mutation wrapper claiming directory.inspect reaches existing mutation dispatch and refuses.",
+ "output": "Require fixed fd1 write-only FIFO before DB open. Opaque result can write only to fd1 after successful checked DB closure, bounded by copied launch deadline and caller cancellation. Nonblocking writes handle backpressure; output/close/expiry errors return3. Partial bytes may exist only in dedicated private pipe and are not a successful result; worker must discard incomplete/nonzero-exit output. No empty-stdout claim after a partial pipe failure.",
+ "review_changes_from_fable_draft": "Kept existing inspection wire instead of replacing its protocol; no breaking NewEngine signature. Private schema callback is set only by RunCommand. Used full schema verification rather than ledger-only. Corrected impossible empty-stdout guarantee after partial output failure. Explicitly validate stdout pipe and honor launch deadline during publication.",
+ "remaining": [
+ "Expanded integrated role/key/schema/refusal/output tests and actual binary dispatch",
+ "Fable review of final implementation and config alignment",
+ "Full local and exact CI gates",
+ "Native platform worker authentication, owner workflow and checkpoint/resume integration",
+ "Deployed canary review before merge; concrete approved live maintenance request before provisioning"
+ ],
+ "canary_failure": "946a24a credential preview succeeded; directory failed at wire-app startup and both previews rolled back to verifiedeab0719. New ordinary-serving exact ledger gate suspected; test reproduces historical additive ledger refusal. Remove that new gate while keeping strict operator/checker gate; no changes to live keys/data or destructive migration. Re-review and canary required."
}
diff --git a/internal/home/operator_schema_linux_test.go b/internal/home/operator_schema_linux_test.go
index 2d308cf..3a3520b 100644
--- a/internal/home/operator_schema_linux_test.go
+++ b/internal/home/operator_schema_linux_test.go
@@ -98,3 +98,22 @@ func TestRuntimeOperatorSchemasAreAdditiveAndRoleSpecific(t *testing.T) {
})
}
}
+
+func TestServingRetainsAdditiveLegacyLedgerButOperatorRefusesIt(t *testing.T) {
+ pair := newExchangePair(t)
+ db := pair.directory.db
+ if _, err := db.Writer().Exec("CREATE TABLE legacy_additive_state(value INTEGER); INSERT INTO rastrillo_migrations(id,applied_at,checksum) VALUES('legacy_additive/0001','2026-01-01T00:00:00Z','historical-checksum')"); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := NewRuntime(*pair.directory.runtime, db, quietLogger()); err != nil {
+ t.Fatal("ordinary serving rejected historical additive migration", err)
+ }
+ conn, err := db.Writer().Conn(t.Context())
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer conn.Close()
+ if err := operator.VerifyLedger(t.Context(), conn, pair.directory.runtime.Schema()); err != operator.ErrRefused {
+ t.Fatal("operator accepted foreign ledger", err)
+ }
+}
diff --git a/internal/home/runtime.go b/internal/home/runtime.go
index ba89464..503709a 100644
--- a/internal/home/runtime.go
+++ b/internal/home/runtime.go
@@ -1,7 +1,6 @@
package home
import (
- "context"
"crypto/elliptic"
"crypto/sha256"
"encoding/base64"
@@ -9,7 +8,6 @@ import (
"errors"
"log/slog"
"os"
- "time"
"amadan.net/oficina/home/handoff"
"amadan.net/oficina/home/internal/home/directorydata"
@@ -173,20 +171,6 @@ func (cfg Runtime) Schema() *migrate.Set {
return BootSchema
}
func NewRuntime(cfg Runtime, d *db.DB, logger *slog.Logger) (*App, error) {
- if cfg.role == "credential" || cfg.role == "directory" {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- conn, err := d.Writer().Conn(ctx)
- if err != nil {
- return nil, errExchangeConfig
- }
- ledgerErr := operator.VerifyLedger(ctx, conn, cfg.Schema())
- closeErr := conn.Close()
- if ledgerErr != nil || closeErr != nil {
- return nil, errExchangeConfig
- }
- }
-
if !cfg.roster.valid(cfg.role, cfg.keys) || !cfg.validAppPreviews() {
return nil, errExchangeConfig
}