docs: preserve reviewed NetKingdom session artifacts
This commit is contained in:
parent
121097524c
commit
cd13c093a3
50 changed files with 7421 additions and 0 deletions
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"path": "workplans/NK-WP-0048-security-case-types.md",
|
||||
"preimage_sha256": "08215f6dc91a89706bf456a897d77de4032d47a21cd0252d202249619def135d",
|
||||
"postimage_sha256": "6f595368aca545c974748b20ceb1caaddfa5162c4542f92a5fb3cb64586fe4cb",
|
||||
"patch_sha256": "af9f720fba1b931679f866a29e8b3bc7d08f24a7f653db48daf485e35cb16eea",
|
||||
"canonical_modified": false,
|
||||
"T20_unchanged": true,
|
||||
"T14_status": "progress",
|
||||
"T14_depends_on": [
|
||||
"NK-WP-0048-T20",
|
||||
"NK-WP-0048-T21"
|
||||
],
|
||||
"T21_status": "done"
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
diff --git a/workplans/NK-WP-0048-security-case-types.md b/workplans/NK-WP-0048-security-case-types.md
|
||||
--- a/workplans/NK-WP-0048-security-case-types.md
|
||||
+++ b/workplans/NK-WP-0048-security-case-types.md
|
||||
@@ -691,31 +691,30 @@
|
||||
```task
|
||||
id: NK-WP-0048-T14
|
||||
status: progress
|
||||
-depends_on: [NK-WP-0048-T20]
|
||||
+depends_on: [NK-WP-0048-T20, NK-WP-0048-T21]
|
||||
priority: high
|
||||
state_hub_task_id: "fce95440-a25b-5f27-8ef3-a3d182099113"
|
||||
```
|
||||
|
||||
-Owner: NetKingdom. Next action: specify a CNPG-backed journal through the
|
||||
-existing `rapp-postgres` database-per-consumer contract, or obtain an explicit
|
||||
-scoped governed exception to accepted ADR-0015 before any production SQLite
|
||||
-proposal. The implemented SQLite backend supplies local/disposable proof only;
|
||||
-a new PVC is not an admitted production path. Prepare the native writer-isolation
|
||||
-contract for the existing identity-provisioner, and bind the
|
||||
-existing protected owner callbacks to the accepted case/source/principal,
|
||||
-per-attempt evidence and session revocation. Identify the actual deployment and
|
||||
-custody owner through the existing ADR-0015 routes before proposing a rollout;
|
||||
-an existing service token, caller-supplied JSON or provenance receipt establishes
|
||||
-no new authorization.
|
||||
-
|
||||
-2026-10-17 refinement: T20 extracts the completed durable provenance source
|
||||
-candidate and disposable LLDAP compatibility proof from this task. The source
|
||||
-returns exact creation inventories, retains prestate/intents across process
|
||||
-restart and refuses detected shared-member/group-identity drift. Its publication
|
||||
-and projection are still pending the coordinator's separate integration receipt.
|
||||
-This narrows T14 without closing its original externally referenced native gate.
|
||||
-The task remains `todo` because preparation of the native contracts can proceed;
|
||||
-no missing owner assent is represented as received.
|
||||
+Owner: NetKingdom. Next action: bind the completed T21 PostgreSQL journal to
|
||||
+an admitted dedicated CNPG consumer through the existing `rapp-postgres`
|
||||
+database-per-consumer contract. Identify the actual deployment/package and
|
||||
+custody owner, admitted placement/capacity and role/renewable lease/TLS target;
|
||||
+implement the independently retained exact content checkpoint and authoritative
|
||||
+tenant/case/source/principal callbacks before enabling governed group operations.
|
||||
+The injection-only backend and fixture callbacks do not supply production scope
|
||||
+or authorization. Existing service tokens, caller-supplied JSON and provenance
|
||||
+receipts confer no new grant. The default production endpoint remains disabled
|
||||
+without the trusted integration. SQLite remains local/disposable only; any
|
||||
+production exception requires explicit scoped governance under ADR-0015.
|
||||
+
|
||||
+2026-10-17 refinement: T20 retains the historical durable SQLite source and
|
||||
+actual disposable LLDAP proof. T21 extracts the completed PostgreSQL source and
|
||||
+bounded real disposable SQL compatibility proof. T14 remains `progress` for
|
||||
+native CNPG admission/recovery, protected owner integration and exclusion of all
|
||||
+external LDAP writers. Separate source publication/projection and independent
|
||||
+cleanup receipts belong to coordinator follow-through; no missing native assent
|
||||
+is represented as received.
|
||||
|
||||
Completion still requires independently reviewed/admitted ADR-0015-conforming
|
||||
CNPG storage (or an explicit governed exception), backup/lost-store recovery and
|
||||
@@ -994,3 +993,73 @@
|
||||
decisions. No package, database, credential, rollout or owner assent was created.
|
||||
Custodian evidence: group-provenance-r1/native-storage-proposal-r1; final source,
|
||||
projection and cleanup review: resume-integration-r1/publication-verification.json.
|
||||
+
|
||||
+
|
||||
+## T21 — Implement the PostgreSQL journal and prove disposable SQL compatibility
|
||||
+
|
||||
+```task
|
||||
+id: NK-WP-0048-T21
|
||||
+status: done
|
||||
+depends_on: [NK-WP-0048-T20]
|
||||
+priority: high
|
||||
+```
|
||||
+
|
||||
+Completed 2026-10-17: bounded source preparation and actual disposable PostgreSQL
|
||||
+compatibility only. T14 retains native storage, custody, callbacks, authorization,
|
||||
+all-writer isolation and production admission. Coordinator source publication and
|
||||
+projection remain separate follow-through; no source revision or task UUID is
|
||||
+invented here. T20 remains the historical local SQLite/actual LLDAP proof.
|
||||
+
|
||||
+The eight-path source candidate adds an injection-only PostgreSQL implementation
|
||||
+behind the existing owner journal seam, with explicit trusted tenant/directory/
|
||||
+case/source/principal/epoch binding and independent continuity callbacks. It
|
||||
+persists bounded intent/created-only receipts, exact-one version updates, durable
|
||||
+active fences, and a content-bound journal chain. SQL commits precede independent
|
||||
+exact-marker acknowledgment; lost replies/checkpoints poison continuation and
|
||||
+retain uncertainty. A counter-only checkpoint is explicitly insufficient for
|
||||
+divergent clones. Migration does not create database/schema/credential, admit an
|
||||
+epoch or clear quarantine; production HTTP activation is absent pending trusted
|
||||
+callbacks. Pinned psycopg package/image-copy closure is included, without claiming
|
||||
+an image build or native dependency admission.
|
||||
+
|
||||
+Candidate base `6cb43c0cc55212363a5406b02db046e2384a7c40`; final eight-path patch
|
||||
+`sha256:5549d2982b61ff70aba4b3928fecd5639db0f1a732ab2f8630f678e7acf4d34d`.
|
||||
+All 82 source tests pass, alongside 13 fixture plan/diagnostic checks and two
|
||||
+source-backed snapshot regressions. Counts overlap the same source and fixture;
|
||||
+they are not additive native acceptance or completion percentages.
|
||||
+
|
||||
+The independently reviewed R5 fixture ran the actual SQL migration/backend and
|
||||
+existing provisioner path against cached pinned disposable PostgreSQL. Directory
|
||||
+operations deliberately used the existing synthetic LLDAP test double. All
|
||||
+manifest-declared phase oracles passed: creation-only inventory and DDL refusal;
|
||||
+separate-process replay/revoke with complete preexisting/shared visible-state
|
||||
+preservation; foreign-context refusal; concurrent exactly-once reservation and
|
||||
+stale version refusal; actual database fork with divergent bodies at the same
|
||||
+counter and fresh-object rejection of the losing exact checkpoint; retained
|
||||
+uncertainty after checkpoint/directory/SQL response loss; and actual pg_dump/
|
||||
+pg_restore rollback refusal against the independently retained fixture checkpoint.
|
||||
+The checkpoint is fixture-only, not an admitted native owner evidence holder.
|
||||
+
|
||||
+Only generated credentials/state were used, with two nonroot/read-only containers
|
||||
+on private loopback, bounded tmpfs/CPU/memory/PIDs/lifetimes, no host ports/mounts,
|
||||
+no pulls and no production contact. The safe receipt reports both exact owned
|
||||
+containers absent, with no daemon uncertainty. Independent final pinned-daemon
|
||||
+cleanup verification remains coordinator follow-through.
|
||||
+
|
||||
+Failed R2/R3/R4 receipts and source revisions remain retained. R2's helper failure
|
||||
+subphase is unknown; R3 exposed a real backend bug where replacing nested receipt
|
||||
+objects lost the created ID held by ensure, fixed by acknowledging only the backend
|
||||
+version after commit/checkpoint. R4 verified that fix through actual SQL bootstrap,
|
||||
+then exposed fixture snapshot aliasing and empty deleted-group backing entries;
|
||||
+source-backed regressions corrected the snapshot without weakening complete
|
||||
+visible-prestate equality, and reject nonempty orphan membership. These refinements
|
||||
+belong to one trial, not extra native acceptance successes.
|
||||
+
|
||||
+Custodian evidence: `docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/`;
|
||||
+exact source/pre/postimages and retained predecessors; native R5 manifest
|
||||
+`sha256:d51f90816616848b93bcdc482e10a98d07061deb01eeed9e50700fc6e68b6051`;
|
||||
+`final-integration-r1/typed-execution-evidence.json` and byte-exact safe receipt
|
||||
+(original `/tmp/nk48-postgres-native-review-sguj7t8p/result.json`). No CNPG native
|
||||
+storage/HA/backup/lease/TLS/capacity/restore admission, authoritative protected
|
||||
+callbacks, real LDAP writer fencing/CAS/ABA exclusion, owner assent or production
|
||||
+endpoint activation is claimed. Those remain live T14 work.
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,996 @@
|
|||
---
|
||||
id: NK-WP-0048
|
||||
type: workplan
|
||||
title: "Establish SecurityCaseTypes so that known security cases are light"
|
||||
domain: infotech
|
||||
repo: net-kingdom
|
||||
status: active
|
||||
flavor: implementation
|
||||
owner: claude-code
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-08"
|
||||
updated: "2026-10-17"
|
||||
depends_on: []
|
||||
related_repos:
|
||||
- ops-mason
|
||||
- ops-warden
|
||||
- key-cape
|
||||
- railiance-platform
|
||||
- informed-decision
|
||||
state_hub_workstream_id: "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb"
|
||||
---
|
||||
|
||||
# NK-WP-0048 - Establish SecurityCaseTypes so that known security cases are light
|
||||
|
||||
## Why
|
||||
|
||||
The founder's direction (2026-10-08): setting up a security integration of a
|
||||
kind that has been done before should be easy, meaning accept the case, then
|
||||
ops-mason arranges it and ops-warden follows through. Setting up a new kind
|
||||
needs ample documentation and tests and an informed-decision, because its shape
|
||||
is part of the security architecture.
|
||||
|
||||
Today every integration is hand-built: per-client helper scripts, per-lane
|
||||
custody, founder checklists. The trigger was KEY-WP-0038 (Forgejo as an OIDC
|
||||
client), whose next step was going to be yet another hand-written rollout
|
||||
script.
|
||||
|
||||
This plan continues the pattern work of NK-WP-0008 (pattern infospace),
|
||||
NK-WP-0009 (tutorials) and NK-WP-0030 (scenario composition). It turns patterns
|
||||
into **SecurityCaseTypes** that ops-mason and ops-warden can execute. Canon:
|
||||
`canon/standards/security-case-types_v0.1.md` (proposed).
|
||||
|
||||
Companion plans: ops-mason `MASON-WP-0010` (materialization) and ops-warden
|
||||
`WARDEN-WP-0044` (follow-through).
|
||||
|
||||
## Tasks
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "930ac3e9-20ec-5473-98cd-3fe8fddb730f"
|
||||
```
|
||||
|
||||
### T01 — Accept the terminology and the canon
|
||||
|
||||
Founder review of `canon/standards/security-case-types_v0.1.md`, including its
|
||||
section 10 open questions. On acceptance the canon status goes from `proposed`
|
||||
to `accepted`.
|
||||
|
||||
2026-10-08: accepted by the founder (in session). Canon status `accepted`;
|
||||
section 10's open questions are carried forward to the first type (T04, T05).
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "19c25d96-a55c-58f0-9168-3fdbc4955fc2"
|
||||
```
|
||||
|
||||
### T02 — Inventory the de-facto types already built by hand
|
||||
|
||||
Read-only. List every security integration built so far and group them by shape.
|
||||
Known starting points:
|
||||
|
||||
- browser OIDC relying parties on key-cape: informed-decision-approver,
|
||||
coulomb-social, user-engine portal, openbao-admin, and Forgejo (pending)
|
||||
- confidential service clients with `client_credentials`: secrets-engine
|
||||
approval, requester and openbao clients, approval-engine operator
|
||||
- workload KV read lanes on OpenBao: the railiance-platform credential lane
|
||||
designs and CCRs, for example `railiance-backup-offsite-lane`
|
||||
- short-lived SSH certificates through ops-warden (`warden sign`)
|
||||
- tunnelled operator access (ops-bridge)
|
||||
|
||||
For each: the repositories involved, the hand-built steps, attended human steps,
|
||||
custody paths, verification evidence, and rollback (if any). Output
|
||||
`docs/security-case-type-inventory.md`, ranking candidate types by how often the
|
||||
shape recurs and how much manual work each instance costs today.
|
||||
|
||||
2026-10-08: done, `docs/security-case-type-inventory.md`. 36 of 37 CCRs are
|
||||
already `request_type: workload-kv-read`, a de-facto type reviewed each time as
|
||||
if it were new. Browser clients: 5 hand-built plus Forgejo, each with its own
|
||||
rollout script. Confidential service clients need a registration plus two or
|
||||
three KV-read CCRs each. A forge read identity is needed in bulk by
|
||||
ORGREF-WP-0002-T06. Recommendation: oidc-browser-relying-party first (unblocks
|
||||
the forge), workload-kv-read second (largest payoff), then forge-read-identity.
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T03
|
||||
status: done
|
||||
depends_on: [NK-WP-0048-T01]
|
||||
priority: high
|
||||
state_hub_task_id: "e487975b-73b1-516c-971d-da4cbbb93063"
|
||||
```
|
||||
|
||||
### T03 — Schema, template and validator
|
||||
|
||||
- `canon/schemas/security-case-type_v0.1.schema.json` and
|
||||
`security-case_v0.1.schema.json`
|
||||
- `security-case-types/_template/` with `type.yaml`, `README.md` and tests to copy
|
||||
- a validator, extending `tools/security-scenario-composer/`, that checks a type,
|
||||
checks a case against its type, and renders the case into the composer's
|
||||
deterministic plan (still `execution.permitted: false`)
|
||||
|
||||
Acceptance: the validator rejects missing sections, secret-shaped parameter
|
||||
values, unpinned providers and cases against non-`implemented` types, with tests
|
||||
for each.
|
||||
|
||||
2026-10-08: done. `canon/schemas/security-case-type_v0.1.schema.json` and
|
||||
`security-case_v0.1.schema.json`; `tools/security-case-types/security_case_validator.py`
|
||||
(`validate-type`, `validate-case`, `render`, reusing the scenario composer);
|
||||
`security-case-types/_template/` (a valid, runnable example type over
|
||||
`net-kingdom.local-identity`, with README sections, an example case and tests that
|
||||
travel with every copy). 31 tests pass, including the composer's existing suite.
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T04
|
||||
status: done
|
||||
depends_on: [NK-WP-0048-T02, NK-WP-0048-T03]
|
||||
priority: high
|
||||
state_hub_task_id: "2fffe112-7d55-5680-9952-78d74f44f87f"
|
||||
```
|
||||
|
||||
### T04 — Define the first type: browser OIDC relying party
|
||||
|
||||
`security-case-types/oidc-browser-relying-party/`, version 1.0:
|
||||
|
||||
- parameters: client id, display name, exact redirect URIs, scopes, client type
|
||||
(confidential or public), MFA pin, the groups the consuming service maps,
|
||||
custody path for the secret (confidential only), consuming service and its
|
||||
owner
|
||||
- composition: key-cape client registration (key-cape declares it as a playbook
|
||||
capability), secret minting into custody (railiance-platform), LLDAP group
|
||||
existence and membership (net-kingdom identity-provisioner), handover to the
|
||||
consuming service
|
||||
- materialization (ops-mason) and follow-through (ops-warden) sections
|
||||
- verification: discovery lists the redirect URI; a test sign-in returns the
|
||||
expected claims for one human; anonymous access to the custody path is denied
|
||||
- rollback: remove exactly this client and its custody path
|
||||
- README: threat model, MFA rationale, why redirect URIs are exact, which steps
|
||||
still need an attended login and how that is minimised
|
||||
- reference instances: informed-decision-approver, coulomb-social, user-engine
|
||||
portal, openbao-admin
|
||||
|
||||
Acceptance: complete per canon section 3, tests green, status `proposed`.
|
||||
|
||||
2026-10-08: done. `security-case-types/oidc-browser-relying-party/` 1.0 (proposed):
|
||||
`type.yaml` with 12 parameters (the custody reference is required only for
|
||||
confidential clients), composition over key-cape's new
|
||||
`key-cape.oidc-client-registration` declaration (contract PASS, `maturity:
|
||||
planned`), materialization and follow-through, verification and rollback, all
|
||||
conditional on client type where it matters. One attended step,
|
||||
`register-client`, justified by the live registry being a Kubernetes Secret.
|
||||
README with threat model, alternatives, an honest fit table (portal and vergabe
|
||||
fit; Forgejo is the first confidential case; openbao-admin, coulomb.social and
|
||||
informed-decision-approver do not fit 1.0), and known limits. Examples: Forgejo
|
||||
(checked against the accepted request) and a public portal. The validator gained
|
||||
`when` / `required_when` conditions and provider discovery. 43 tests pass.
|
||||
ops-mason's and ops-warden's sections are drafted in the type. Their owners
|
||||
review them in MASON-WP-0010-T01 and WARDEN-WP-0044-T01 before acknowledgment
|
||||
(T05).
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T05
|
||||
status: done
|
||||
depends_on: [NK-WP-0048-T04, MASON-WP-0010-T01, WARDEN-WP-0044-T01]
|
||||
priority: high
|
||||
state_hub_task_id: "3d987d3d-0797-538b-86e4-51185a327ae2"
|
||||
```
|
||||
|
||||
### T05 — Acknowledge the type through informed-decision
|
||||
|
||||
Prepare the Decision Memo from the type's README and `type.yaml` and submit it
|
||||
through informed-decision's production intake. On acknowledgment the type
|
||||
moves to `acknowledged`.
|
||||
|
||||
2026-10-08: Decision Memo prepared. Hub decision `e418263e-a71f-4f01-a970-c87c9e187c09`.
|
||||
`security-case-types/oidc-browser-relying-party/decision/`: `packet.md` (question,
|
||||
one act, what acknowledging does not do, the shape, alternatives, attended
|
||||
steps, limits, evidence), `build_intake.py` and the built `intake.json` (memo
|
||||
class `decision-record`, review group `net-kingdom-admins`, resolves the hub
|
||||
decision on answer). The envelope passes informed-decision's own
|
||||
`validate_submission`. Queuing it is an attended operator step, a write inside
|
||||
the production dispatcher pod:
|
||||
|
||||
kubectl -n state-hub exec -i deploy/state-hub-memo-dispatch -- \
|
||||
python -m api.services.memo_dispatch --queue /data/private enqueue --envelope /dev/stdin \
|
||||
< security-case-types/oidc-browser-relying-party/decision/intake.json
|
||||
|
||||
The answer then appears for review in informed-decision.
|
||||
|
||||
2026-10-08: queued by the founder (`pending`, `queued`) after one refusal: the
|
||||
first envelope's idempotency key was not State Hub's stable
|
||||
`state-hub.<decision>.<memo>` form, and the builder now validates against both
|
||||
sides. The dispatcher delivered it to informed-decision (ledger 7 → 8
|
||||
delivered). T05 waits for the founder's answer in informed-decision, which
|
||||
resolves hub decision `e418263e`.
|
||||
|
||||
2026-10-08: ops-mason's review (MASON-WP-0010-T01) folded into the type, still
|
||||
1.0 `proposed`: `secret_ref` is no longer a parameter, the type derives
|
||||
`platform/workloads/keycape/{client_id}`; policies are
|
||||
`workload-kv-read-keycape-{client_id}-eso` and
|
||||
`workload-kv-read-keycape-{client_id}-{consuming_service}`, each with a templated
|
||||
body (`read` on that one data path); new `openbao_role`
|
||||
`external-secrets-keycape-{client_id}` (Kubernetes auth, ESO service account,
|
||||
15m, no default policy); catalog entry `{consuming_service}-keycape-client`;
|
||||
the two rollback steps on the KV path collapse into one metadata delete. The
|
||||
schema and validator now require a `body` on policy and role items. 105 tests
|
||||
pass. Two consequences: the delivered memo describes the type before these
|
||||
changes (a case-set custody reference, two differently named policies, no ESO
|
||||
role), so the founder answers on a superseded shape unless the memo is
|
||||
re-sent; and Forgejo's custody path moves from the accepted request's proposed
|
||||
`platform/workloads/forgejo/netkingdom-oidc-client` to
|
||||
`platform/workloads/keycape/forgejo`, which key-cape (KEY-WP-0038) and
|
||||
railiance-apps (RAPPS-WP-0016-T02) need to adopt in T06. ops-warden's review
|
||||
(WARDEN-WP-0044-T01) is still in progress.
|
||||
|
||||
2026-10-08: revised memo `nk-wp-0048-t05-oidc-browser-relying-party-r2` built
|
||||
(`packet.md`, `intake.json`, packet `sha256:af6435e2…`); it states that it
|
||||
supersedes the first memo and passes informed-decision's and the dispatcher's
|
||||
validation. It needs a new memo id because the dispatcher refuses new content
|
||||
under a delivered idempotency key (`idempotency_conflict`). Queuing it is the
|
||||
same attended step as above. Both memos serve hub decision `e418263e`; the
|
||||
first answer resolves it, so the first memo must not stay open: an accept on it
|
||||
would acknowledge the superseded shape, and a decline would resolve the
|
||||
decision against r2. Close it with `return` (a reviewer verb that requests a
|
||||
new revision and does not resolve the hub decision; only accept and decline
|
||||
do), commenting that r2 supersedes it, then answer r2. `withdraw` exists in
|
||||
the vocabulary but is not offered to reviewers, and lifecycle `revise` only
|
||||
applies to lifecycle-registered memos, not intake-delivered ones.
|
||||
|
||||
2026-10-09: the founder reports r2 enqueued and the first memo returned. At
|
||||
close, hub decision `e418263e` still showed `open` (not verified whether r2
|
||||
was answered or the answer is still in transit; the dispatcher ledger was not
|
||||
read). ops-warden's review (WARDEN-WP-0044-T01, ops-warden
|
||||
`history/2026-10-08-oidc-rp-follow-through-review.md`, d470f2e) arrived at
|
||||
21:25, before r2 was built, and is not in r2. Required before WARDEN-WP-0044-T02:
|
||||
R1 a per-step `lane` (missing: identity-provisioner caller credential,
|
||||
railiance-platform broker grant), R2 canon meaning of `warden_executes: true`
|
||||
(caller proxy, caller identity, no standing ops-warden token; ADR-0002), R3 a
|
||||
per-step `value_handling`. Refinements R4 (one type-level catalog lane instead
|
||||
of a per-case entry, which conflicts with ops-mason's item 5), R5 (ESO Ready gate
|
||||
before bind-env), R6 (exact accepted memberships), R7 (mfa-pinned and
|
||||
secret-delivered checks). Next: if r2 was accepted, fold R1-R3 and R5-R7 in as
|
||||
a follow-up and settle R4 between ops-warden and ops-mason before T06; if not,
|
||||
hold r2, fold them in and build r3.
|
||||
key-cape and railiance-apps were told about Forgejo's new custody
|
||||
path.
|
||||
|
||||
2026-10-09: r2 was not answered (hub decision `e418263e` open; dispatcher 9
|
||||
delivered), so r2 is held and r3 replaces it. ops-warden withdrew R4 for R4′
|
||||
(message a66a9204): one catalog entry per case with its body templated in the
|
||||
type. Folded in:
|
||||
|
||||
- Canon section 3: a narrow meaning of `warden_executes: true` (caller proxy,
|
||||
caller identity, no standing credential; ops-warden ADR-0002), plus per-step
|
||||
`lane`, `value_handling` and an optional `ready_check`. A catalog entry body is
|
||||
now required, like policy and role bodies.
|
||||
- Schema and validator enforce all of these.
|
||||
- The type: lanes `identity-provisioner-groups` and
|
||||
`keycape-client-secret-generate` (both new) and `key-cape-oidc-login`;
|
||||
`mint-secret` is `generated_in_custody`; a Ready gate on `deliver-secret`;
|
||||
`ensure-groups` adds exactly the accepted case's memberships; new checks
|
||||
`secret-delivered` and `mfa-pinned`; the catalog-entry body follows R4′.
|
||||
- 110 tests pass.
|
||||
|
||||
Memo r3 (`nk-wp-0048-t05-oidc-browser-relying-party-r3`, packet
|
||||
`sha256:726dfc01…`) passes informed-decision's and the dispatcher's validation.
|
||||
The founder queues it with the same command, returns r2 with a comment that r3
|
||||
supersedes it, then answers r3.
|
||||
|
||||
Prerequisites for T06, not for T05:
|
||||
- the two new lanes, each through its owner;
|
||||
- a founder-approved rollout of an identity-provisioner image containing
|
||||
630ebfd (the deployed one is main-6c4fcaf from 2026-09-24, without
|
||||
`/v1/groups/ensure`).
|
||||
|
||||
ops-mason's request to create LLDAP group `fluid-telegram-operators` (message
|
||||
259ec26d) was answered: not possible from here by that path; the founder creates
|
||||
it in the LLDAP admin UI.
|
||||
|
||||
2026-10-09: ops-warden finished its side of WARDEN-WP-0044-T01 (message
|
||||
`58d33f40`, commit 0e7ceb8): the revised type conforms to `warden case
|
||||
check-type`, with no further changes asked of the type. This acknowledges that
|
||||
review. Its later addendum R8 (message `af274783`, ops-warden
|
||||
`history/2026-10-08-oidc-rp-follow-through-review.md`) showed that lane
|
||||
`identity-provisioner-groups` cannot exist as declared: the provisioner's only
|
||||
HTTP credential is user-engine's runtime token, and its NetworkPolicy admits
|
||||
only user-engine pods, so a caller-proxy lane would act as user-engine. Taken
|
||||
option 1 (ops-warden's recommendation): `ensure-groups` and its rollback are
|
||||
attended, in the `register-client` session on `key-cape-oidc-login`, run inside
|
||||
the provisioner pod; no token is read. Option 2 (an operator credential of its
|
||||
own on the provisioner) stays open for a later version. `warden case
|
||||
check-type` now reports only `keycape-client-secret-generate`
|
||||
(railiance-platform) as missing. 89 tests pass.
|
||||
|
||||
Hub decision `e418263e` was still open, so memo r4
|
||||
(`nk-wp-0048-t05-oidc-browser-relying-party-r4`, packet `sha256:452ea421…`)
|
||||
replaces r3; it passes informed-decision's and the dispatcher's validation. The
|
||||
founder queues it with the same command, returns r2/r3 if still open with a
|
||||
comment that r4 supersedes them, then answers r4.
|
||||
|
||||
Prerequisites for T06 are now: lane `keycape-client-secret-generate`
|
||||
(railiance-platform), and a founder-approved rollout of an identity-provisioner
|
||||
image containing 630ebfd.
|
||||
|
||||
2026-10-09: done. The founder queued r4 (`pending`, `queued`), returned r3 as
|
||||
superseded and accepted r4 in informed-decision. informed-decision's hub
|
||||
write-back is off in production (`INFD_HUB_WRITEBACK` unset), so net-kingdom
|
||||
resolved hub decision `e418263e` from the founder's report, naming memo r4 in
|
||||
the rationale. The type is `acknowledged`; it becomes `implemented` with T06.
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T06
|
||||
status: wait
|
||||
depends_on: [NK-WP-0048-T05, NK-WP-0048-T08, NK-WP-0048-T09, NK-WP-0048-T12, NK-WP-0048-T13, NK-WP-0048-T14, NK-WP-0048-T15, NK-WP-0048-T16, NK-WP-0048-T17, NK-WP-0048-T18, NK-WP-0048-T20, MASON-WP-0010-T03, WARDEN-WP-0044-T03]
|
||||
blocking_reason: "Type1.2 architecture has a confirmed source-only founder acknowledgment; canonical1.1 remains unchanged/default and the1.2 reference case stays proposed. Needs explicit version selection/reference-source integration without normal admission bypass, closed owner adapters plus global/native receipt verifier (WARDEN-WP-0044-T03), independently verified six-object structural materialization and CCR42 claims/created-only provenance, exact broker/case/source reconciliation, then separately authorized native mint/delivery and attended registry/consumer verification. Source acknowledgment, a bundle or local tests do not prove implemented/materialized/live status."
|
||||
priority: high
|
||||
state_hub_task_id: "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
|
||||
```
|
||||
|
||||
### T06 — First KnownSecurityCase: Forgejo
|
||||
|
||||
Write `security-cases/forgejo-netkingdom-oidc.yaml` from the parameters the
|
||||
founder accepted in KEY-WP-0038-T01 (`key-cape/docs/forgejo-oidc-client-request.yaml`),
|
||||
so that acceptance carries over as the case acceptance. ops-mason materializes it;
|
||||
ops-warden follows through and verifies it. The type becomes `implemented` once
|
||||
this reference case is live.
|
||||
|
||||
This replaces the hand-built path proposed for KEY-WP-0038-T02.
|
||||
|
||||
2026-10-09: case written, `security-cases/forgejo-netkingdom-oidc.yaml`, status
|
||||
`accepted`: the example's parameters plus the founder's 2026-10-08 acceptance of
|
||||
the key-cape request (owner and founder), with the custody-path derivation
|
||||
covered by the type's acknowledgment (memo r4). It validates and renders (plan
|
||||
`execution.permitted: false`; three attended steps on `key-cape-oidc-login`);
|
||||
it needs `--allow-unimplemented` until it is live, since it is the case that
|
||||
makes the type `implemented`. A test keeps it equal to the type's example.
|
||||
Still open before it runs: ops-mason renders and builds it (MASON-WP-0010-T02,
|
||||
T03, unblocked by T05), ops-warden plans and runs it (WARDEN-WP-0044-T02, T03),
|
||||
railiance-platform's `keycape-client-secret-generate` lane (asked by ops-mason
|
||||
`789feb39` and ops-warden `d9f889b1`, both unread), and a founder-approved
|
||||
identity-provisioner rollout containing 630ebfd.
|
||||
|
||||
2026-10-09: identity-provisioner rolled out, founder-approved in session:
|
||||
`main-630ebfd` (`sha256:bafb4860…`, built by the image workflow) replaces
|
||||
`main-6c4fcaf` (`sha256:ffacd5d7…`; only change: 630ebfd, additive). Applied
|
||||
with `kubectl set image` on the Deployment only (generation 17 → 18, 1/1 Ready);
|
||||
the file's Service, NetworkPolicies and Ingress were not reapplied. Verified in
|
||||
the pod: `ensure_service_groups` and `revoke_service_groups` present, `/readyz`
|
||||
200; no token used, no group changed. Rollback: `kubectl -n sso set image
|
||||
deploy/identity-provisioner provisioner=forgejo.coulomb.social/coulomb/identity-provisioner@sha256:ffacd5d790a5b7800e0535f3aa95b7de52c7a68e4f575b9192c3e1622fa672c2`.
|
||||
The pin in `sso-mfa/k8s/identity-provisioner/deployment.yaml` follows.
|
||||
|
||||
2026-10-09: ops-warden closed WARDEN-WP-0044-T01 and shipped `warden case plan`
|
||||
(T02, message `06303d1d`); the accepted Forgejo case is refused until ops-mason
|
||||
materializes it, and a materialized copy plans cleanly once
|
||||
`keycape-client-secret-generate` exists. Its observation that `bind-env` carries
|
||||
a literal `KEYCAPE_<CLIENT_ID>_CLIENT_SECRET`: the validator now renders a
|
||||
`{param:env}` format spec (upper case, `-` as `_`, as `keycape-client.py`
|
||||
derives the name) and rejects unknown specs, and follow-through and rollback
|
||||
text is now checked for unknown placeholders too (patch-level, canon section 6).
|
||||
The type keeps the literal until ops-warden's planner (`text.format`) renders the
|
||||
spec as well; asked of ops-warden. 53 tests pass.
|
||||
|
||||
2026-10-10: ops-warden rendered `{param:env}` in `warden case plan`
|
||||
(23ba584, message `c1df14b7`; other specs and `!conversion` refused). The type
|
||||
adopts it as a 1.0 patch (canon section 6): `bind-env` and its rollback now read
|
||||
`KEYCAPE_{client_id:env}_CLIENT_SECRET`. ops-mason reports the Forgejo case
|
||||
rendered and built (message `1ebddae9`; CCR drafts handed to railiance-platform),
|
||||
so T06 now waits on the platform apply, the mint-secret lane and the attended
|
||||
register-client session.
|
||||
|
||||
2026-10-10: key-cape adopted the type's custody path (key-cape ab5cccd, message
|
||||
`88a4559c`): `docs/forgejo-oidc-client-request.yaml` and KEY-WP-0038-T02 name
|
||||
`platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, delivered as
|
||||
`sso/keycape-forgejo-client` through `external-secrets-keycape-forgejo`, and the
|
||||
`forgejo/netkingdom-oidc-client` proposal is withdrawn. This matches the type and
|
||||
the Forgejo example; no change needed here.
|
||||
|
||||
2026-10-10: key-cape's type-composition review (key-cape 6038483, message
|
||||
`8e5ed5f6`) found the composition sound with two blocking findings, taken as a
|
||||
1.0 patch (stricter validation and documentation, canon section 6):
|
||||
R1, redirect URIs now refuse `?` and `#` in the type pattern and in
|
||||
`keycape-client.py`'s `REDIRECT` (KeyCape refuses `?` at startup and the single
|
||||
replica would stop); R2, `allowed_scopes` always includes `groups`, the renderer
|
||||
refuses `access_groups` without it, and `access_groups` now says the consumer
|
||||
enforces the gate because KeyCape has none; R3, `mfa_required` "false" means the
|
||||
per-user privacyIDEA policy; R4, register-client's justification records the
|
||||
restart for every client, the quiet window, and that the rendered registry must
|
||||
pass KeyCape's client validation before it. The accepted Forgejo case and both
|
||||
examples still validate; 116 tests pass.
|
||||
Open, needs a decision: R2's verification step that the consumer enforces the
|
||||
group gate is a new verification step, hence 1.1 (minor), and the validator pins
|
||||
cases to the exact type version, so the accepted 1.0 Forgejo case would need
|
||||
re-pinning. Also open with key-cape: a KeyCape entry point that validates a
|
||||
rendered client registry without starting the server; until then
|
||||
`keycape-client.py` mirrors the redirect rule.
|
||||
|
||||
2026-10-10: founder go for 1.1, so no 1.0 run overlaps. The type adds
|
||||
verification `consumer-enforces-groups`, and hand-over now requires the consumer
|
||||
to enforce `access_groups` (minor, canon section 6; risk class unchanged, no new
|
||||
acknowledgment; history in the type README). The accepted Forgejo case and both
|
||||
examples are re-pinned to `oidc-browser-relying-party@1.1` before any apply; the
|
||||
acknowledgment intake keeps `@1.0` as the record of what was acknowledged.
|
||||
ops-mason's build and ops-warden's case-ref digest predate this and need a
|
||||
re-render.
|
||||
|
||||
2026-10-10: ops-mason re-rendered the Forgejo case from `@1.1` (message
|
||||
`22c6bba6`; plan digest `af1f4f81…`, no collisions) and rebuilt the bundle. The
|
||||
rendered OpenBao objects are identical to 1.0, so the already applied
|
||||
CCR-2026-0041 stands; 1.1 changes only consumer-side steps. key-cape shipped
|
||||
`keycape check-config` (c528daa, message `a7b0aeff`): KeyCape's startup checks
|
||||
without secret values, including the new duplicate-clientId rule.
|
||||
`keycape-client.py --check-config <binary>` now runs it on the live and the
|
||||
proposed registry, fed on stdin, and apply refuses a proposed registry that
|
||||
fails; register-client's entry point names the option (1.1 patch). The deployed
|
||||
image predates c528daa, so the binary is built from key-cape source for now.
|
||||
Before the next KeyCape rollout (Forgejo register-client or the CCR-2026-0018
|
||||
window), the attended operator runs `inspect --check-config` so the live
|
||||
registry is checked too.
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T07
|
||||
status: wait
|
||||
depends_on: [NK-WP-0048-T06]
|
||||
blocking_reason: "The next types follow once the first one is proven end to end."
|
||||
priority: medium
|
||||
state_hub_task_id: "c3c9c3ae-4f23-5ba7-9e6f-e63bcf29b9f7"
|
||||
```
|
||||
|
||||
### T07 — Queue the next types
|
||||
|
||||
From the T02 ranking, open the next type drafts (likely the confidential service
|
||||
client and the workload KV read lane), each to be acknowledged on its own.
|
||||
Record which existing hand-built instances get adopted as cases, without
|
||||
rewriting them in place.
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T08
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "8e519082-dc8b-5e46-b660-03d03e55dcf6"
|
||||
```
|
||||
|
||||
### T08 — identity-provisioner: ensure service-access groups
|
||||
|
||||
The type's `ensure-groups` step calls `identity-provisioner groups ensure --groups
|
||||
<list> --members <group=user+user;...>`. The provisioner already has idempotent
|
||||
`_ensure_group` and `_add_group`, but exposes only tenant-scoped operations. Add
|
||||
the general operation (create if absent, add the listed memberships, never
|
||||
remove others), its rollback (remove only this case's memberships), tests, and
|
||||
the ops-warden route for its service identity.
|
||||
|
||||
2026-10-08: done. `identity-provisioner/provisioner.py` gained
|
||||
`ensure_service_groups` (`POST /v1/groups/ensure`) and `revoke_service_groups`
|
||||
(`POST /v1/groups/revoke`), behind the existing service-token check. Names
|
||||
matching `tenant*`, `netkingdom-*` and `lldap_*` are reserved; members must
|
||||
already exist; everything is validated before any change; memberships are read
|
||||
back before success; nothing else is ever removed; groups are deleted only when
|
||||
asked and empty. 7 new tests (39 in the provisioner suite). The type's
|
||||
`ensure-groups` step and its rollback now name these endpoints. The ops-warden
|
||||
route for the provisioner's service token belongs to WARDEN-WP-0044-T01.
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T09
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "0ee12dfc-8260-5038-bd39-cc7fb4113be0"
|
||||
```
|
||||
|
||||
### T09 — The `keycape-client` entry point and its declaration
|
||||
|
||||
Planned first as key-cape KEY-WP-0038-T04. Moved here because the machinery a
|
||||
live registration needs already lives in net-kingdom, where KeyCape is deployed
|
||||
(`sso-mfa/k8s/keycape/`): the contained kubectl, the cluster-identity pin and the
|
||||
issuer pin. A copy in key-cape would drift.
|
||||
|
||||
2026-10-08: done. `sso-mfa/k8s/keycape/keycape-client.py` derives the KeyCape
|
||||
client record from a case (`client-from-case`). It adds or removes exactly one
|
||||
client with the existing safeguards (byte-preserving merge, exact match or
|
||||
refuse, shared-reference refusal, compare-and-swap, read-back, contained errors),
|
||||
requires the Deployment env binding first for confidential clients, and requires
|
||||
the case acceptance record for `apply` instead of a per-client founder decision
|
||||
id. 15 tests, including a regression showing that the vergabe case reproduces
|
||||
its hand-built registration exactly; all 71 helper tests pass. Declaration
|
||||
`capabilities/playbooks/net-kingdom.keycape-client-registration.yaml` (contract
|
||||
PASS, `maturity: implemented`) replaces key-cape's draft. The type now pins it,
|
||||
and for confidential clients adds `deliver-secret` (delivery from custody into
|
||||
the sso namespace) and `bind-env` (Deployment env), attended in the same session
|
||||
as `register-client`. The per-client helpers stay until their clients are
|
||||
adopted as cases.
|
||||
|
||||
2026-10-10 — NetKingdom owner review of the execution seam, source only:
|
||||
ops-warden/docs/security-case-execution-proposal.md SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
|
||||
was reviewed against INTENT, SecurityCase canon, acknowledged type1.1, current
|
||||
case and owner source. docs/security-case-execution-owner-review.md supports
|
||||
the closed adapter/receipt direction with explicit versioned schema, structural
|
||||
materialization provenance, unchanged caller/session boundaries, safe CAS
|
||||
reconciliation and exact-created-object rollback requirements. This is owner
|
||||
review, not approval. Supporting file digests are in the adjacent sources JSON.
|
||||
|
||||
Current source exposes a circular wait: ops-mason T03 waits on mint while Warden
|
||||
and the broker refuse a non-materialized case. Structural acceptance therefore
|
||||
needs its own native pinned receipt before mint, not a successful mint before
|
||||
materialized. The platform broker additionally pins type1.0 while Forgejo is
|
||||
accepted at1.1; platform-owner alignment and tests are required, not an unchecked
|
||||
version bypass. T06 remains wait. No case/type lifecycle status, grant, authority
|
||||
or live runtime changed by this review. A proposed informed-decision would
|
||||
adopt the source contract only; native materialization and execution acceptance
|
||||
stay distinct.
|
||||
|
||||
Final proposal re-review: staged Warden proposal SHA256 78ab1439e4c5beb205aeccf15b1b2e706511be55ab195c5a3aa682f326c7c77c
|
||||
adds only the explicitly local reducer appendix, not an accepted wire protocol
|
||||
or executor. The exact-final review and sources JSON supersede the earlier
|
||||
1035f049... proposal review. Platform staged the exact type1.1 broker correction
|
||||
with ten fake-backend subprocess checks (script SHA256
|
||||
9576cd211cfc06526ec4cc9bd0a5b06f0f59287a68931bb2d6e3592a951ddc7d).
|
||||
The next source work is the versioned closed adapter/receipt/materialization
|
||||
contract, not another relaxation of the broker's status gate. Native work still
|
||||
requires installed broker provenance, structural reader receipts, exact new
|
||||
materialized-case bytes, caller grant/session revocation and verified execution.
|
||||
No installed/native acceptance is inferred from that staged correction.
|
||||
|
||||
2026-10-11 — new source-only type1.2 architecture acknowledgment:
|
||||
Confirmed native founder ACCEPT of infd-20261011-forgejo-type12-source-r1@1
|
||||
at06:47:39Z, approval8e9332c8-754a-420c-b8b7-445cb4fe6765. Exact frozen packet
|
||||
0b377d647ca748cd03c92f9792396412048055d4bde9f7bb06ff6d413f7acd68 and submission
|
||||
585cee731de30d2b37883a57abfd9eb32f023ee694e8d911989109d5caf80569; native receipt
|
||||
retained in docs/evidence/2026-10-11-forgejo-type12-source-acknowledgment/.
|
||||
The complete acknowledged1.2 source package is isolated under
|
||||
security-case-type-revisions/1.2/oidc-browser-relying-party; default loader and
|
||||
current accepted1.1 case remain untouched because load_type is single-version.
|
||||
The new proposed1.2 case is NOT accepted/materialized/live and has no acceptance
|
||||
fields. Actual reference-migration design approval is a separate Apps owner
|
||||
receipt; no1.1 approval is inherited. Source-recording review/test proof and exact
|
||||
next gates are in docs/forgejo-type12-source-acknowledgment-followthrough.md.
|
||||
T06 remains wait and T07 remains its existing dependent task; type is NOT
|
||||
implemented and no first-case/native grant or broader workplan finish is asserted.
|
||||
|
||||
2026-10-11 — bounded explicit version selection source preparation for T06:
|
||||
The validator now offers optional exact version_roots/--version-root VERSION=PATH
|
||||
selection with duplicate/mismatch/traversal/symlink refusal and no fallback. The
|
||||
default1.1 source and historicalacceptedcase hashes/ordinaryrefusal/preparation
|
||||
render stayunchanged; actual1.1 is acknowledged, not implemented. Acknowledged1.2
|
||||
ordinaryadmission also refuses; optionalpreparation remains nonexecuting.43local
|
||||
validatorchecks pass and original1.1 CLIrenderbytes match exactly. Makerproof:
|
||||
docs/evidence/2026-10-11-explicit-type-version-selection/source-integration-validation.json.
|
||||
FormalNetownerreview/reference-source integration and all existing native adapter,
|
||||
provenance/broker/materialization/attendedproof gates remain; T06 stayswait, no
|
||||
new residual or firstcase/typeimplementation/native grant is inferred.
|
||||
|
||||
|
||||
2026-10-16 — isolated source preparation for T06 (canonical integration pending):
|
||||
Added `check-evidence` to the existing validator CLI. It reuses exact version
|
||||
selection and ordinary type/case/render admission, checks actual Mason structural
|
||||
request source/inventory/projection pins and reads actual Warden journal rows
|
||||
read-only. Successful rows require exact type-declared descriptors and attempt/
|
||||
revocation fingerprints. Existing acknowledged types without machine descriptors
|
||||
remain plan-only. The compatibility result never claims authenticated native
|
||||
completion: Request and journal fingerprints are not portable native evidence.
|
||||
Owner authentication/status binding and actual native gates remain unresolved;
|
||||
T06/T07 statuses and both type/case lifecycles remain unchanged. No dispatch,
|
||||
credential access or authority expansion is introduced. See tool README for the
|
||||
existing owner protocol boundaries and source integration tests.
|
||||
|
||||
Concrete next Warden owner integration: current `Executor.admit` refuses every
|
||||
case parameter whose sensitivity is not public/operational, so browser
|
||||
`redirect_uris`, `allowed_scopes`, `client_type`, `mfa_required` (and 1.2
|
||||
`consumer_namespace`) fail after otherwise prepared admission. The sensitive
|
||||
classification is a security-change review level, not secret content. The
|
||||
Warden owner must validate through NetKingdom's existing exact-version case/type
|
||||
validator, then project only descriptor-declared safe target metadata; do not
|
||||
remove the credential/value refusal or copy complete parameters into journals.
|
||||
Actual owner descriptor acknowledgment and independent evidence admission remain
|
||||
prerequisites, with normal admission still refusing unimplemented types.
|
||||
|
||||
|
||||
## T10 — Source compatibility checker for Mason and Warden evidence
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T10
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "4b06ca97-c1e4-5680-bf22-ab75ab1646a4"
|
||||
```
|
||||
|
||||
Completed 2026-10-16 in published source `c31d21ba`. The read-only
|
||||
`check-evidence` command checks the actual Mason structural request and Warden
|
||||
journal source protocols against an exact version/render. Independent source
|
||||
review passed 103 tests at that checkpoint. It always reports native completion
|
||||
false and execution prohibited. Fixtures and local journal fingerprints do not
|
||||
provide authenticated owner/native evidence. This records completed source scope
|
||||
previously described under T06; it does not duplicate native acceptance credit.
|
||||
|
||||
## T11 — Validate the optional machine-descriptor pin contract
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T11
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ef2a0119-f59e-5e45-9519-115da9df7b85"
|
||||
```
|
||||
|
||||
Completed 2026-10-16. Reproduced the JSON schema rejecting a valid
|
||||
`execution_descriptors` map while the Python validator accepted malformed maps.
|
||||
The schema now supports the existing Warden optional exact-byte pin map; semantic
|
||||
validation binds keys to declared steps, bounds the map and checks digest shape.
|
||||
The direct evidence API refuses malformed maps before accessing them. Twenty-one
|
||||
new checks and all 127 affected source checks passed. No current type/case bytes,
|
||||
status, owner descriptors or native acceptance changed. Source evidence and
|
||||
publication receipt: Custodian
|
||||
`docs/changes/netkingdom-target-2026-10-16/browser-acceptance-next/`.
|
||||
|
||||
## T12 — Prepare NetKingdom-owned attended step adapters
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T12
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "7ce4eee3-cd47-55a9-bfa5-4f1835d4d6fd"
|
||||
```
|
||||
|
||||
Owner: NetKingdom. Retained scope: prepare bounded attended Deployment env,
|
||||
config verification and restart/readiness steps around the existing helpers.
|
||||
Next action: identify exact current entry points and their revision/readback
|
||||
contracts, then implement fixed source-pinned callbacks or receipt-only attended
|
||||
steps. Completion requires positive/refusal checks, exact target/source pins and
|
||||
uncertainty behavior without acquiring user-engine runtime identity or exposing
|
||||
registry/client secrets. Existing caller/session, cluster/issuer, CAS/readback
|
||||
and status-only output boundaries remain mandatory.
|
||||
|
||||
2026-10-16 task refinement: the original broad adapter scope is split into T13
|
||||
(completed registration source candidate), T14 (group creation provenance), T15
|
||||
(durable registration recovery) and T16 (descriptor/artifact selection and owner
|
||||
review). This preserves T12's task ID and Hub UUID while giving each remaining
|
||||
outcome an owner and completion gate. T06 now names every source dependency;
|
||||
its native/reference lifecycle acceptance scope remains unchanged. Completion
|
||||
of any source task supplies no Warden, Platform or Apps assent and no native
|
||||
execution authority.
|
||||
|
||||
## T13 — Prepare the fixed registration adapter source candidate
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T13
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "b264c10b-565c-5d29-b788-03cf5fbd2883"
|
||||
```
|
||||
|
||||
Completed 2026-10-16, source implementation and synthetic fixture proof only.
|
||||
Added an injection-only adapter around actual `keycape-client.run`: fixed
|
||||
case/principal/human/lane/source/acceptance binding, existing CAS/readback,
|
||||
metadata-only owner evidence and per-attempt parent-session revocation boundary.
|
||||
The descriptor source composition pins the exact checker executable digest
|
||||
alongside adapter/helper source. Creation-only rollback authenticates its
|
||||
creation receipt and refuses changed postcreation UID/resourceVersion;
|
||||
pre-existing clients confer no rollback authority. Missing readback, evidence,
|
||||
revocation or process-local creation provenance cannot become retry-safe success.
|
||||
|
||||
Evidence: Custodian `docs/changes/netkingdom-target-2026-10-16/browser-adapter-next/`
|
||||
R3 candidate, explicit patch and postimages, retained R1 rejected custody finding
|
||||
and R2 revision, and 36 passing source checks (16 new, 20 existing). Three checks
|
||||
use actual Warden kernel admission/dispatch/rollback with published Warden
|
||||
`d97652a3bb5fa733b063d71a1b74068efcccf572` producer pins unchanged. Owner trust,
|
||||
descriptor/type/case and Kubernetes objects remain synthetic fixtures. This
|
||||
records a completed source deliverable, not independent review, publication,
|
||||
type acknowledgment, exercised procedure, identity admission or native browser
|
||||
acceptance. Independent root source review: Custodian browser-adapter-next/coordinator-review-r3.json. Source integration and normal publication/synchronization receipts: Custodian browser-adapter-next/canonical-integration.json. These receipts supply no other-owner assent or native authority.
|
||||
|
||||
Source publication: `0775c2bfa41af236301155bb5a69bbf088bdf823`, independently
|
||||
reviewed by the Custodian coordinator before integration. The required initial
|
||||
projection and ordinary UUID writeback both completed with zero failures and
|
||||
zero automation errors. Generated-only brief commits were refused by the normal
|
||||
hook; exact pre-existing staged/working brief bytes were restored. The separate
|
||||
canonical integration receipt retains those refusals and projection evidence.
|
||||
Native admission and all other-owner acceptance remain open.
|
||||
|
||||
## T14 — Admit governed group rollback against native owner boundaries
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T14
|
||||
status: progress
|
||||
depends_on: [NK-WP-0048-T20]
|
||||
priority: high
|
||||
state_hub_task_id: "fce95440-a25b-5f27-8ef3-a3d182099113"
|
||||
```
|
||||
|
||||
Owner: NetKingdom. Next action: specify a CNPG-backed journal through the
|
||||
existing `rapp-postgres` database-per-consumer contract, or obtain an explicit
|
||||
scoped governed exception to accepted ADR-0015 before any production SQLite
|
||||
proposal. The implemented SQLite backend supplies local/disposable proof only;
|
||||
a new PVC is not an admitted production path. Prepare the native writer-isolation
|
||||
contract for the existing identity-provisioner, and bind the
|
||||
existing protected owner callbacks to the accepted case/source/principal,
|
||||
per-attempt evidence and session revocation. Identify the actual deployment and
|
||||
custody owner through the existing ADR-0015 routes before proposing a rollout;
|
||||
an existing service token, caller-supplied JSON or provenance receipt establishes
|
||||
no new authorization.
|
||||
|
||||
2026-10-17 refinement: T20 extracts the completed durable provenance source
|
||||
candidate and disposable LLDAP compatibility proof from this task. The source
|
||||
returns exact creation inventories, retains prestate/intents across process
|
||||
restart and refuses detected shared-member/group-identity drift. Its publication
|
||||
and projection are still pending the coordinator's separate integration receipt.
|
||||
This narrows T14 without closing its original externally referenced native gate.
|
||||
The task remains `todo` because preparation of the native contracts can proceed;
|
||||
no missing owner assent is represented as received.
|
||||
|
||||
Completion still requires independently reviewed/admitted ADR-0015-conforming
|
||||
CNPG storage (or an explicit governed exception), backup/lost-store recovery and
|
||||
migration of legacy requests; a native
|
||||
writer-isolation/fencing or provider CAS/version contract that closes external
|
||||
writer races and remove/re-add ABA; exact case/source/principal and current
|
||||
native object bindings for protected callbacks; and actual per-attempt evidence,
|
||||
revocation and readback/refusal verification through the existing owner paths.
|
||||
Only independently retained confirmed creations may be undone. Every
|
||||
pre-existing/shared group and membership must remain preserved; unresolved
|
||||
intents never expire into retry authority. The disposable fixture supplies none
|
||||
of these native custody, authorization, owner-acceptance or production claims.
|
||||
|
||||
## T15 — Define authenticated recovery for lost registration process state
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T15
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "1b825705-4848-5edc-832f-b1622cd9bdb7"
|
||||
```
|
||||
|
||||
Owner: NetKingdom for registration state, with Warden owning kernel evidence
|
||||
admission. Next action: select the exact existing owner evidence holder/readback
|
||||
contract and define authenticated lookup for the original creation attempt.
|
||||
Completion requires restart/lost-receipt fixtures that bind case/source/principal,
|
||||
original created-only provenance and current native revision before any undo;
|
||||
uncertain outcomes remain non-replayable. The T13 adapter deliberately refuses
|
||||
post-restart rollback and installs no reconciliation callback. A new broker or
|
||||
unverified JSON receipt is not a recovery contract. Actual evidence-holder
|
||||
acceptance and attended recovery remain separately authorized native work.
|
||||
|
||||
## T16 — Select and review the actual machine descriptor composition
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T16
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "200bf500-5084-5ae7-be26-3033fe1f296e"
|
||||
```
|
||||
|
||||
Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden
|
||||
independently owns global provenance/admission. Next action: obtain review of the
|
||||
exact checker source/artifact proposed by T17 and identify native owner evidence/
|
||||
session callback contracts, then assemble the complete step descriptor set and
|
||||
a proposed explicit type version selection. T13's historical descriptor retains
|
||||
its synthetic checker; T17 supplies an artifact-bound proposal, not admission.
|
||||
Completion requires separate owner source review of exact machine descriptors,
|
||||
artifact/source/config pins, normal type/version admission and independent
|
||||
evidence-verification boundaries. Warden d97652a refreshes producer validator
|
||||
pins for NetKingdom 015694ee; it does not attest T13's new adapter. Pinning any
|
||||
machine descriptor into an acknowledged type is a separately reviewed source
|
||||
change. Platform owns structural/mint/broker/delivery evidence; Apps owns actual
|
||||
consumer login/group enforcement. Neither their assent nor native acceptance is
|
||||
supplied by this task.
|
||||
|
||||
2026-10-16 source checkpoint: T19 below extracts the completed per-attempt
|
||||
executable snapshot implementation and reviewed fixture proof. T16 retains
|
||||
native executable storage/custody, exact owner evidence/session callbacks,
|
||||
complete descriptor composition and ordinary type/version admission. The
|
||||
successor descriptor/source pins are proposed only; the earlier descriptor
|
||||
history is preserved. T14 group provenance and T15 durable restart/recovery
|
||||
remain independent open requirements. No native acceptance is counted here.
|
||||
|
||||
## T17 — Build and exercise the exact offline config-checker artifact
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T17
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "8c3e4baa-a218-5996-af9e-fe1153a46dac"
|
||||
```
|
||||
|
||||
Completed 2026-10-16, isolated artifact/source fixture proof only. Built the
|
||||
existing `key-cape/src/cmd/keycape` command from published clean source revision
|
||||
`3c71226a1a7b8a2bdb710e9c497e1a1f7f8a3987`, source tree
|
||||
`f0010b9cab17dbc3a787628a29657d98b900320f`, with cached Go 1.23.0,
|
||||
CGO disabled, Linux/amd64 v1, trimmed paths, disabled VCS stamping and offline
|
||||
read-only module selection. The full retained closure pins 108 source files,
|
||||
9876 toolchain files and ten used module source directories. Three isolated
|
||||
build locations produced identical 13633409-byte artifacts:
|
||||
`sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`.
|
||||
|
||||
Eight disposable checks use the actual executable and maintained helper/kernel,
|
||||
with complete synthetic configs and a minimal environment: add/created-only
|
||||
removal, normal pinned producer admission, rollback, lost-readback uncertainty,
|
||||
stale revisions, substituted checker refusal, invalid startup config and missing
|
||||
secret-env refusal. No checker stub or producer-pin override remains. The
|
||||
artifact-bound descriptor proposal is
|
||||
`sha256:76b1153d188062ece53fcd5e8d35290fdbfd02a69dd79d4b001fc42781afcdc3`.
|
||||
Evidence: Custodian
|
||||
`docs/changes/netkingdom-target-2026-10-16/browser-checker-artifact-next/`.
|
||||
The first two build attempts succeeded but their closure collection failed;
|
||||
both failures are retained, as is refusal of the older incomplete synthetic
|
||||
fixture. No production config, credentials or cluster mutation was used.
|
||||
|
||||
This extracts completed build/fixture scope from T16 without claiming KeyCape
|
||||
owner endorsement, native checker selection/storage custody, acknowledged type
|
||||
integration, native evidence/session callbacks or attended browser acceptance.
|
||||
T16 retains exact owner review and composition/admission scope. The attended
|
||||
registration procedure remains unexercised.
|
||||
|
||||
## T18 — Make checker input and executable selection deterministic
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T18
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "9c45da71-fb4c-5f20-9065-90f448f37c64"
|
||||
```
|
||||
|
||||
Completed 2026-10-16, isolated source correction and real-artifact fixture proof
|
||||
only; independent integration/publication remain separately recorded. KeyCape
|
||||
source review identified non-secret `KEYCAPE_ACCOUNT_PORTAL_URL` and
|
||||
`KEYCAPE_BROWSER_LOGOUT_URL` overrides in config.Load. The original helper
|
||||
inherited ambient environment, allowing valid supplied configuration to be
|
||||
rejected or invalid supplied URL configuration to be admitted. A bare relative
|
||||
checker filename could also be replaced by PATH lookup after the adapter had
|
||||
hashed the selected local file. All three counterexamples are retained against
|
||||
the unchanged predecessor.
|
||||
|
||||
The existing helper now resolves the selected checker file to an absolute path
|
||||
and passes an explicit empty subprocess environment. Three opt-in real-checker
|
||||
regressions verify both URL-verdict directions and relative-file selection under
|
||||
a non-checker PATH decoy. All 39 affected checks pass with the exact supplied
|
||||
artifact, as do 11 real-artifact helper/kernel scenarios. Source evidence:
|
||||
Custodian `docs/changes/netkingdom-target-2026-10-16/browser-checker-environment-r2/`.
|
||||
Historical descriptors remain retained; the helper correction changes successor
|
||||
source-composition pins and requires their separate review/admission. An absolute
|
||||
path is not immutable custody and does not eliminate file replacement races;
|
||||
T16 retains native executable storage/custody and callback/type admission.
|
||||
No production configuration, credentials or cluster mutation was used.
|
||||
|
||||
Source integration and publication: `916ba4b33e03179274d03b78bd898a4843e8653f`, following independent
|
||||
Custodian source review in browser-checker-environment-r2/coordinator-review.json.
|
||||
The ordinary protected projection pass assigned T17/T18 UUIDs with zero failures
|
||||
and zero automation errors; generated-only brief commit refusal is retained.
|
||||
The exact prior staged/working brief bytes were restored. Final source/publication/
|
||||
projection evidence is retained in Custodian browser-checker-environment-r2/
|
||||
canonical-integration.json. These source receipts do not admit native executable
|
||||
custody, owner callbacks, acknowledged type changes or browser identities.
|
||||
|
||||
## T19 — Bind checker execution to verified immutable attempt bytes
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T19
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "f50fdcd1-00be-5c3c-be0c-345aa2126032"
|
||||
```
|
||||
|
||||
Completed 2026-10-16, independently reviewed source implementation and synthetic
|
||||
fixture proof only. The registration adapter now opens a bounded regular checker
|
||||
artifact without following its terminal symlink, verifies the reviewed digest,
|
||||
and snapshots the exact bytes into a write/grow/shrink/seal-removal-sealed Linux
|
||||
memfd before helper access. Live and proposed config checks execute that same fd
|
||||
through procfs with the existing empty environment; the fd closes on completion
|
||||
or failure. Unsupported snapshot/seal/exec policy refuses through the existing
|
||||
parent-session revocation and uncertainty boundaries. The earlier unbounded
|
||||
pre-snapshot path read was removed during independent review; actual adapter
|
||||
symlink/FIFO/oversize refusal tests verify helper access never starts.
|
||||
|
||||
Evidence: Custodian `docs/changes/netkingdom-target-2026-10-16/browser-resume-r1/`,
|
||||
including exact base/preimages/postimages/patch, proposed descriptor, coordinator
|
||||
source review and canonical integration receipt. Independent Custodian checks
|
||||
passed 49 affected source tests and 12 actual Go checker/helper/kernel scenarios;
|
||||
the sets overlap and are not separate acceptance counts. Existing published
|
||||
Warden d97652a producer pins remain unchanged. The exact KeyCape artifact remains
|
||||
`sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5`.
|
||||
The predecessor path-replacement refusal and the first expanded reopened-fd test
|
||||
failure are retained; the latter fixture was corrected to restore write mode
|
||||
before independently proving seals block reopened-fd writes and shared mmap.
|
||||
|
||||
This source task adds no native owner custody, callback, evidence trust, type
|
||||
acknowledgment, credential access, consumer sign-in or rollback acceptance.
|
||||
Executable memfds, Linux seals and usable procfs are explicit runtime dependencies
|
||||
and still require native admission. This is an extracted source outcome from
|
||||
T16, not completion of T16; T12/T14/T15 and all external-owner gates remain open.
|
||||
|
||||
|
||||
Publication verified by the coordinator after the source worker usage limit:
|
||||
`8790dd18ae83c4651ef3d1d923312a118a5b436b` is the actual remote main revision.
|
||||
All five canonical source hashes match the reviewed packet. Normal synchronization
|
||||
completed with zero failures/automation errors; all 19 task statuses match the
|
||||
projection. T19's assigned UUID is retained. The original staged and working brief
|
||||
bytes were restored and independently compared. The exact source, projection and
|
||||
publication checks are in Custodian browser-resume-r1/canonical-integration.json.
|
||||
Native acceptance remains open under T16 and the other named owner tasks.
|
||||
|
||||
|
||||
## T20 — Prepare durable group provenance and prove disposable LLDAP compatibility
|
||||
|
||||
```task
|
||||
id: NK-WP-0048-T20
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "863fcbdb-e246-51ba-87ca-a2f44d01d6f8"
|
||||
```
|
||||
|
||||
Completed 2026-10-17, source candidate preparation and bounded synthetic
|
||||
compatibility proof only. This extracts evidenced implementation/fixture scope
|
||||
from T14; T14 retains native storage, writer isolation, protected callback and
|
||||
owner-acceptance gates. Source integration/publication and State Hub projection
|
||||
are pending the coordinator's separate receipts; no revision or task UUID is
|
||||
invented here.
|
||||
|
||||
The existing identity-provisioner group ensure/revoke interface now has a
|
||||
reviewed candidate for a bounded owner-private SQLite journal, proven only in
|
||||
local/disposable use. The initial storage instructions wrongly suggested a native
|
||||
persistent-volume path; source review against accepted ADR-0015 corrected them
|
||||
before publication. Production SQLite-on-a-PVC is not admitted: T14 retains
|
||||
CNPG/rapp-postgres storage admission or a separately governed exception.
|
||||
The local journal saves
|
||||
complete prestate and intent before each attempted directory mutation, inventories
|
||||
only confirmed new groups/memberships, binds original ensure idempotency and
|
||||
correlation to the exact request digest, and refuses unsigned caller inventories
|
||||
or legacy broad revoke requests. Replays never recreate removed state. Missing,
|
||||
replaced or untrusted storage and missing intent rows stop execution; definite
|
||||
before-mutation refusals retain evidence without stranding unrelated valid work.
|
||||
Interrupted writes/readback remain unresolved across reopening. Revoke validates
|
||||
all targeted native ids and complete member postimages before destructive work,
|
||||
removes only confirmed creations and never deletes a preexisting empty group.
|
||||
Server/configuration wiring, image-copy closure and owner storage/request
|
||||
migration instructions are included. The existing endpoint's caller
|
||||
authentication remains the existing boundary, not a new grant.
|
||||
|
||||
The candidate was prepared against NetKingdom
|
||||
`878abc93588e12b6da1bac055cfbb3abb8d7adf1`; exact source patch digest
|
||||
`sha256:1f3f3ca94c46dadac8f24896053d4d23179de795e50a24298a0cfbcc46cca30e`.
|
||||
The independent Custodian review passed all 65 provisioner checks. The earlier
|
||||
62-check candidate, two independent-review findings and subsequent fixes remain
|
||||
retained rather than overwritten. Seven additional fixture-plan checks exercise
|
||||
the preparation boundary; these counts overlap the same candidate and do not
|
||||
measure task-completion percentage or independent acceptance.
|
||||
|
||||
A separately reviewed bounded fixture then exercised the exact candidate against
|
||||
cached pinned disposable LLDAP and Python images, through actual GraphQL. All 14
|
||||
closed compatibility/containment oracles passed: exact creation inventory,
|
||||
separate-process SQLite reopening/replay/revoke, complete preexisting directory
|
||||
preservation including empty/shared groups and memberships, refusal of shared
|
||||
membership drift, retained uncertainty after a lost response following a real
|
||||
addition, and refusal of unknown/cross-case receipt bindings. The fixture used
|
||||
only newly generated synthetic users/passwords/key material in private tmpfs,
|
||||
with a network-none anchor and shared loopback helper, no host ports/mounts,
|
||||
no image pulls and no production or actual credential contact. The coordinator's
|
||||
safe receipt reports both exact owned containers absent after cleanup; final
|
||||
independent cleanup verification remains the coordinator's integration duty.
|
||||
|
||||
Evidence: Custodian
|
||||
`docs/changes/netkingdom-target-2026-10-16/group-provenance-r1/`, including exact
|
||||
source base/pre/postimages, patch/manifest, independent review and retained
|
||||
refinements. Native fixture manifest
|
||||
`sha256:8a0514f5f14137355972400cd4852f48c892316d28e3abd3c86635d7cb81eef2`;
|
||||
retained safe result `native-fixture/execution-result.json` (original
|
||||
`/tmp/nk48-groups-native-review-5pywb19z/result.json`). The source task supplies
|
||||
no native owner assent, production rollout, protected session/evidence callbacks,
|
||||
ordinary type admission or consumer login proof. LLDAP has no conditional
|
||||
membership mutation or generation token here: exact readback cannot exclude
|
||||
ABA or a concurrent external writer, so T14 remains open.
|
||||
|
||||
2026-10-17 verified source follow-through: source commit `4f65e41` passed normal
|
||||
local scanning and all 65 canonical provisioner tests. The actual disposable
|
||||
LLDAP receipt passed all 14 oracles; independent verification on the pinned
|
||||
daemon found both exact names and the run label absent. Source integration
|
||||
includes the ADR-0015 documentation correction; the journal remains local-only.
|
||||
|
||||
T14 source preparation is underway. Twenty-three pinned public sources identify
|
||||
NetKingdom implementation/reconciliation, Platform custody/database governance
|
||||
and rapp-postgres consumer provisioning; the inspected runtime binding does not
|
||||
assign identity-provisioner a package. The existing User Engine runtime token
|
||||
and pod route are not a protected-case caller lane. The native proposal preserves
|
||||
the same journal seam on a dedicated CNPG consumer and requires authoritative
|
||||
scope binding, admitted role/lease/placement/recovery and all LDAP writers fenced.
|
||||
The declared exclusive maintenance window is not mechanical exclusion of WebUI,
|
||||
helper or lifecycle writers. Capacity placement and EUR cost/risk remain owner
|
||||
decisions. No package, database, credential, rollout or owner assent was created.
|
||||
Custodian evidence: group-provenance-r1/native-storage-proposal-r1; final source,
|
||||
projection and cleanup review: resume-integration-r1/publication-verification.json.
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
# Prepared disposable PostgreSQL compatibility fixture
|
||||
|
||||
Status: prepared and unit checked; not executed or approved. Default
|
||||
`python3 driver.py --plan` reads only the manifest, without loading containment,
|
||||
generating material or calling Docker. Execution requires independent review of
|
||||
the exact manifest/source closure and explicit execution flag/digest. The flag
|
||||
and fixture files confer no authority.
|
||||
|
||||
The fixture reuses the existing `isolated-keycape.py` command/environment guard,
|
||||
containment inspector and exact name/label cleanup. A network-none PostgreSQL
|
||||
anchor and a pinned cached User Engine Python helper share only loopback. Both
|
||||
are nonroot/read-only/capability-free, with private bounded tmpfs, no host ports,
|
||||
mounts or Docker socket, and no pulls/builds/packages. Generated admin/runtime
|
||||
passwords enter through stdin only. Image-specific nonroot UID999/Python/psycopg
|
||||
compatibility is an explicit precondition checked by execution; no cache or
|
||||
runtime claim is made during preparation. Any incompatible image/anonymous
|
||||
volume/error fails closed and still invokes exact cleanup.
|
||||
|
||||
Six sequential Python phases use the actual candidate SQL backend and its
|
||||
normal provisioner path with the **existing synthetic LDAP Directory**. Bootstrap
|
||||
runs the real migration on generated state, checks runtime DDL denial and exact
|
||||
created-only inventories. A separate process reopens and replays/revokes while
|
||||
preserving the complete synthetic prestate. Concurrent reservations must yield
|
||||
one original receipt; stale row CAS cannot advance its head. Two actual database
|
||||
clones then pass the same old checkpoint check concurrently and commit different
|
||||
receipts at the same next counter. Only one external digest CAS may win; a fresh
|
||||
backend on the losing clone must refuse even at that same counter. Lost checkpoint
|
||||
acknowledgment, lost synthetic directory response and a lost reply after actual
|
||||
SQL commit must retain pending/uncertain state and refuse continuation. An actual
|
||||
pg_dump taken after bootstrap is then pg_restored after later journal changes;
|
||||
the external test checkpoint survives. Runtime SQL SELECT must work and show an
|
||||
older restored head before the candidate's restore-quarantine refusal oracle can
|
||||
pass. This prevents an unrelated connection/ACL failure from masquerading as
|
||||
restore protection.
|
||||
|
||||
All fresh names/labels are registered before create. `finally` uses the existing
|
||||
cleanup helper for both resources, requires successful exact absence inventories
|
||||
and retains any Docker uncertainty as failed. PID1 lifetimes and RPC/execution
|
||||
limits are bounded in the manifest. This is not a parent-death/reaper proof.
|
||||
Only safe booleans, public image/source/version pins and cleanup duties leave in
|
||||
a private `/tmp/nk48-postgres-native-review-*/result.json`; synthetic backup bytes
|
||||
are bounded in host memory and never written as an archive file.
|
||||
|
||||
The test checkpoint implementation belongs only to this fixture. Its independent
|
||||
file survives PostgreSQL restore and uses file locking/fsync; it is not offered
|
||||
as native custody, case authority, a journal-recovery API or an evidence broker.
|
||||
The fixture explicitly disposes one definitely pre-LDAP synthetic reservation to
|
||||
move between fault cases; it does not clear an unknown native mutation. All
|
||||
source/fixture passes remain distinct from live CNPG/lease/TLS/backup continuity,
|
||||
protected owner callbacks, actual authoritative tenant scope, native LDAP fencing
|
||||
and production acceptance.
|
||||
|
||||
|
||||
## R2 cached helper selection
|
||||
|
||||
The reviewed R1 preflight failed before `execute`, session allocation or private
|
||||
import because its pinned helper image 1e6547 was absent. No pull or override was
|
||||
used. Frozen manifest/driver/probe/source and the coordinator's structured failure
|
||||
are retained under `revisions/r1`. There is no raw stderr file; the original
|
||||
traceback was returned in tool output only, so no byte-exact stderr artifact is
|
||||
invented.
|
||||
|
||||
The proposed R2 helper is the coordinator-observed cached f239 image, bound by
|
||||
existing published receipts to User Engine source 1bb7cacc. That source uses
|
||||
system pip on official Python 3.12, not a virtualenv. R2 therefore binds every
|
||||
helper launch/exec to `/usr/local/bin/python3`; it has no PATH or alternate-venv
|
||||
fallback. A closed in-container probe must verify the exact executable and import
|
||||
psycopg 3 before candidate source import, reporting the bundled version. Missing
|
||||
or mismatched interpreter/dependency refuses and preserves exact cleanup duties.
|
||||
No container count, limit, privilege, network or cleanup expansion is proposed.
|
||||
Historical producer or rollout acceptance is not a new image/dependency grant.
|
||||
See `helper-selection-r2.json`, `manifest-delta-r2.json` and `preparation-r2.json`.
|
||||
|
||||
|
||||
## R3 bounded failure diagnostics
|
||||
|
||||
R2 executed and failed after containment and interpreter verification (bundled
|
||||
psycopg 3.3.6); all phase checks were empty. Its unchanged receipt and manifest
|
||||
are preserved in `revisions/r2`. The old driver discarded a failed helper result,
|
||||
so the exact failing subphase is unknown. Bootstrap is not asserted as the cause.
|
||||
|
||||
R3 adds fixed public driver/RPC markers and helper NDJSON markers, retained before
|
||||
an oracle raises. PostgreSQL initdb now completes synchronously with only a fixed
|
||||
boolean result before the detached server launch. Bootstrap separately identifies
|
||||
connection readiness, migration, epoch insertion, role/grants, checkpoint and
|
||||
ensure/readback. Failures expose only an allowlisted exception class and optional
|
||||
five-character SQLSTATE, including a wrapped cause; never exception messages,
|
||||
SQL/DSN/password values, command arguments or database logs. Public frames are
|
||||
bounded to 32768 bytes and 96 lines; RPC trace is bounded to 192 fixed rows. The
|
||||
safe result is checkpointed before/after each RPC and on helper failure. This
|
||||
remains a sequential fixture, with unchanged images, authority, backend source,
|
||||
oracles, container/network limits and exact cleanup requirements. No retry or
|
||||
runtime execution is authorized by this diagnostic revision.
|
||||
|
||||
|
||||
## R4 demonstrated receipt-reference fix
|
||||
|
||||
R3 reached bootstrap ensure and failed before readback, with fixed RuntimeError
|
||||
class and no SQLSTATE. Its safe receipt and full source/fixture are retained.
|
||||
Local tests demonstrated the candidate's save method detached the provisioner's
|
||||
nested group-state reference while acknowledging a journal version. R4 changes
|
||||
only that method to update the acknowledged PostgreSQL version in place after
|
||||
commit/checkpoint success. The original failing regression and complete real
|
||||
provisioner ensure path pass locally with the fix. No diagnostic/fixture driver,
|
||||
phase oracle, SQL privilege, resource, image, network or cleanup change is made.
|
||||
The runtime failure is not claimed to be fully resolved until reviewed execution.
|
||||
|
||||
|
||||
## R5 faithful synthetic directory snapshots
|
||||
|
||||
R4 passed all five real PostgreSQL bootstrap checks, then failed during restart
|
||||
without a fine-grained marker. Its safe receipt and fixture/source are frozen.
|
||||
Local execution of the existing Directory/provisioner proved two fixture snapshot
|
||||
errors: returning the mutable group mapping let ensure contaminate the prestate
|
||||
before JSON serialization; and the test double retains empty membership backing
|
||||
sets for successfully deleted groups even though `_directory` no longer exposes
|
||||
their IDs. R5 copies the group map and snapshots full memberships for every
|
||||
current group ID. It explicitly refuses any nonempty orphan, rather than ignoring
|
||||
unknown membership. Two local source-backed tests reproduce the old failure,
|
||||
prove exact detached prestate after real ensure/revoke, and reject changed shared
|
||||
memberships or nonempty orphans. The semantic complete visible-prestate equality
|
||||
and all phase oracle names remain intact. Restart now has fixed load/reopen,
|
||||
replay/revoke/readback/prestate/foreign-context markers. Backend source5549, image
|
||||
pins, resources and exact cleanup remain unchanged; actual compatibility awaits
|
||||
independent exact review and execution.
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
"""Closed public fixture diagnostics. Never serialize exception text or SQL values."""
|
||||
import json
|
||||
import re
|
||||
|
||||
DRIVER_STAGES = ('session_registered', 'anchor_launch', 'anchor_containment', 'postgres_private_import',
|
||||
'postgres_initdb', 'postgres_launch', 'anchor_identity', 'helper_launch', 'full_containment',
|
||||
'helper_interpreter', 'candidate_import', 'bootstrap', 'restart', 'concurrency', 'fork', 'lost-ack',
|
||||
'restored', 'database_dump', 'database_restore', 'cleanup', 'complete')
|
||||
PROBE_STAGES = ('probe_entry', 'loopback_check', 'phase_dispatch', 'bootstrap_connect_wait',
|
||||
'bootstrap_connected', 'bootstrap_schema', 'bootstrap_migration', 'bootstrap_epoch_insert',
|
||||
'bootstrap_role_create', 'bootstrap_runtime_grants', 'bootstrap_checkpoint_init',
|
||||
'bootstrap_directory_init', 'bootstrap_ensure', 'bootstrap_ensure_readback',
|
||||
'bootstrap_runtime_ddl_refusal', 'bootstrap_complete', 'restart_load_snapshot',
|
||||
'restart_reopen_store', 'restart_replay_ensure', 'restart_revoke', 'restart_revoke_readback',
|
||||
'restart_prestate_equality', 'restart_foreign_context', 'restart_complete', 'phase_complete')
|
||||
ERROR_CLASSES = ('GroupProvenanceUnavailable', 'RuntimeError', 'ValueError', 'TypeError', 'KeyError',
|
||||
'FileNotFoundError', 'PermissionError', 'OperationalError', 'ProgrammingError', 'IntegrityError',
|
||||
'InsufficientPrivilege', 'InvalidPassword', 'UndefinedTable', 'UndefinedColumn', 'SyntaxError',
|
||||
'DuplicateTable', 'DuplicateObject', 'InvalidSchemaName', 'DeadlockDetected', 'ConnectionException',
|
||||
'InvalidAuthorizationSpecification', 'OSError', 'TimeoutExpired', 'ImportError',
|
||||
'ModuleNotFoundError', 'JSONDecodeError', 'OtherError')
|
||||
|
||||
|
||||
def error_metadata(error, stage):
|
||||
if stage not in DRIVER_STAGES + PROBE_STAGES:
|
||||
stage = 'phase_dispatch'
|
||||
name = type(error).__name__
|
||||
name = name if name in ERROR_CLASSES else 'OtherError'
|
||||
state = None
|
||||
current = error
|
||||
for _ in range(8):
|
||||
if current is None:
|
||||
break
|
||||
try:
|
||||
candidate = getattr(current, 'sqlstate', None)
|
||||
if isinstance(candidate, str) and re.fullmatch('[0-9A-Z]{5}', candidate):
|
||||
state = candidate
|
||||
break
|
||||
current = current.__cause__ or current.__context__
|
||||
except Exception:
|
||||
break
|
||||
return {'stage': stage, 'exception_class': name, 'sqlstate': state}
|
||||
|
||||
|
||||
def emit_stage(stage):
|
||||
if stage not in PROBE_STAGES:
|
||||
raise RuntimeError('invalid_public_fixture_stage')
|
||||
print(json.dumps({'event': 'stage', 'stage': stage}), flush=True)
|
||||
|
||||
|
||||
def parse_frames(raw, *, require_final=True):
|
||||
if isinstance(raw, bytes):
|
||||
raw = raw.decode('utf-8')
|
||||
if not isinstance(raw, str) or len(raw) > 32768:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
stages, final = [], None
|
||||
lines = raw.splitlines()
|
||||
if len(lines) > 96:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
for line in lines:
|
||||
value = json.loads(line)
|
||||
if not isinstance(value, dict) or final is not None:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
if value.get('event') == 'stage':
|
||||
if set(value) != {'event', 'stage'} or value['stage'] not in PROBE_STAGES:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
stages.append(value['stage'])
|
||||
elif value.get('status') in {'passed', 'failed'} and final is None:
|
||||
if value['status'] == 'failed':
|
||||
if set(value) != {'status', 'failure', 'diagnostic'} or value['failure'] != 'synthetic_pg_oracle_failed':
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
diagnostic = value['diagnostic']
|
||||
if set(diagnostic) != {'stage', 'exception_class', 'sqlstate'} or diagnostic['stage'] not in PROBE_STAGES or diagnostic['exception_class'] not in ERROR_CLASSES:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
if diagnostic['sqlstate'] is not None and not re.fullmatch('[0-9A-Z]{5}', str(diagnostic['sqlstate'])):
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
elif (set(value) != {'status', 'checks', 'psycopg_version', 'active_database'}
|
||||
or not isinstance(value['checks'], dict)
|
||||
or not isinstance(value['psycopg_version'], str)
|
||||
or not re.fullmatch(r'3\.[0-9]+\.[0-9]+(?:[a-z]+[0-9]+)?', value['psycopg_version'])
|
||||
or value['active_database'] not in {'postgres', 'fixture_fork'}):
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
final = value
|
||||
else:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
if require_final and final is None:
|
||||
raise RuntimeError('missing_public_fixture_result')
|
||||
return stages, final
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Default is a read-only plan. Docker execution awaits independent exact review."""
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import secrets
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
MANIFEST = HERE / 'manifest.json'
|
||||
|
||||
# Fixed local diagnostic parser; no dependency or credential discovery.
|
||||
_spec = importlib.util.spec_from_file_location('fixture_diagnostic_protocol', HERE / 'diagnostic_protocol.py')
|
||||
diagnostics = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(diagnostics)
|
||||
|
||||
|
||||
def require(condition):
|
||||
if not condition:
|
||||
raise RuntimeError('bounded_synthetic_fixture_refused')
|
||||
|
||||
|
||||
def reviewed_manifest(expected):
|
||||
raw = MANIFEST.read_bytes()
|
||||
require(expected == hashlib.sha256(raw).hexdigest())
|
||||
manifest = json.loads(raw)
|
||||
for path, digest in manifest['source_sha256'].items():
|
||||
require(bool(re.fullmatch(r'sha256:[0-9a-f]{64}', digest)))
|
||||
require('sha256:' + hashlib.sha256(Path(path).read_bytes()).hexdigest() == digest)
|
||||
return manifest
|
||||
|
||||
|
||||
def load_containment(manifest):
|
||||
spec = importlib.util.spec_from_file_location('reviewed_existing_containment', manifest['containment_helper'])
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def session_plan(manifest, run_id):
|
||||
require(bool(re.fullmatch(r'[a-f0-9]{32}', run_id)))
|
||||
anchor = 'nk49-kc-' + run_id + '-journal-postgres'
|
||||
probe = 'nk49-kc-' + run_id + '-journal-probe'
|
||||
return {'run_id': run_id, 'containers': [anchor, probe], 'worker': anchor, 'issuer': probe,
|
||||
'images': {anchor: manifest['postgres_image'], probe: manifest['python_image']}, 'volumes': {}}
|
||||
|
||||
|
||||
def base_args(session, name, network, user="1000:1000"):
|
||||
uid, gid = user.split(":")
|
||||
return ['docker', 'run', '--detach', '--pull=never', '--name', name, '--network', network,
|
||||
'--label', 'net-kingdom.recovery-run=' + session['run_id'], '--read-only', '--cap-drop', 'ALL',
|
||||
'--security-opt', 'no-new-privileges', '--user', user, '--memory', '384m', '--cpus', '0.5',
|
||||
'--pids-limit', '64', '--log-driver', 'none', '--restart', 'no',
|
||||
'--tmpfs', '/tmp:rw,nosuid,nodev,noexec,size=16777216,uid=' + uid + ',gid=' + gid + ',mode=0700']
|
||||
|
||||
|
||||
def tar_bytes(files):
|
||||
out = io.BytesIO()
|
||||
with tarfile.open(fileobj=out, mode='w') as archive:
|
||||
for name, value in files.items():
|
||||
require(bool(re.fullmatch(r'[a-zA-Z0-9._/-]+', name)) and not name.startswith('/') and '..' not in Path(name).parts)
|
||||
entry = tarfile.TarInfo(name)
|
||||
entry.size = len(value)
|
||||
entry.mode = 0o600
|
||||
entry.uid = entry.gid = 1000
|
||||
archive.addfile(entry, io.BytesIO(value))
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
IMPORT = r'''
|
||||
import base64,json,os,sys
|
||||
from pathlib import Path
|
||||
os.umask(0o077)
|
||||
root=Path('/fixture')
|
||||
(root/'source').mkdir(mode=0o700)
|
||||
(root/'source/migrations').mkdir(mode=0o700)
|
||||
(root/'source/tests').mkdir(mode=0o700)
|
||||
for name,encoded in json.load(sys.stdin).items():
|
||||
if name not in ('source/provisioner.py','source/group_provenance.py','source/group_provenance_postgres.py','source/migrations/group_journal_postgres_v1.sql','source/tests/test_service_groups.py','probe.py','private.json','diagnostic_protocol.py'):raise ValueError()
|
||||
fd=os.open(root/name,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
with os.fdopen(fd,'wb') as output:output.write(base64.b64decode(encoded,validate=True))
|
||||
'''
|
||||
|
||||
|
||||
def helper_python(manifest):
|
||||
# Exact producer source installs into official Python's system prefix.
|
||||
# Never fall back to PATH or another interpreter/virtual environment.
|
||||
require(manifest.get('helper_interpreter') == '/usr/local/bin/python3')
|
||||
return manifest['helper_interpreter']
|
||||
|
||||
|
||||
INTERPRETER_PROBE = r"""
|
||||
import json,sys
|
||||
try:
|
||||
import psycopg
|
||||
valid=sys.executable=='/usr/local/bin/python3' and psycopg.__version__.split('.')[0]=='3'
|
||||
print(json.dumps({'helper_ready':valid,'executable':sys.executable,'psycopg_version':psycopg.__version__}))
|
||||
except Exception:
|
||||
print(json.dumps({'helper_ready':False}))
|
||||
"""
|
||||
|
||||
|
||||
def preflight(adapter, manifest):
|
||||
helper_python(manifest)
|
||||
adapter.docker_environment() # Refuse ambient alternate routing before calls.
|
||||
contexts = json.loads(adapter.command(['docker', 'context', 'inspect'], timeout=15))
|
||||
require(len(contexts) == 1 and contexts[0]['Endpoints']['docker']['Host'] == manifest['fixed_endpoint'])
|
||||
facts = json.loads(adapter.command(['docker', 'info', '--format',
|
||||
'{"ID":{{json .ID}},"Name":{{json .Name}},"OSType":{{json .OSType}}}'], timeout=15))
|
||||
require(facts == {'ID': manifest['engine_id'], 'Name': manifest['engine_name'], 'OSType': 'linux'})
|
||||
for image in (manifest['postgres_image'], manifest['python_image']):
|
||||
value = adapter.command(['docker', 'image', 'inspect', '--format', '{{.Id}}', image], timeout=15).decode().strip()
|
||||
require(bool(re.fullmatch(r'sha256:[a-f0-9]{64}', value)))
|
||||
|
||||
|
||||
def retain_helper_result(result, phase, raw, publish):
|
||||
stages, value = diagnostics.parse_frames(raw)
|
||||
safe = {'status': value['status'], 'stages': stages}
|
||||
if value['status'] == 'failed':
|
||||
safe['diagnostic'] = value['diagnostic']
|
||||
result.setdefault('helper_diagnostics', {})[phase] = safe
|
||||
publish() # Persist failure evidence before the phase oracle can raise.
|
||||
return value
|
||||
|
||||
|
||||
def execute(manifest, adapter, result, checkpoint=None):
|
||||
session = session_plan(manifest, uuid.uuid4().hex)
|
||||
result['session'] = {'run_id': session['run_id'], 'containers': session['containers'],
|
||||
'label': 'net-kingdom.recovery-run=' + session['run_id']}
|
||||
python = helper_python(manifest)
|
||||
deadline = time.monotonic() + 210
|
||||
original_command = adapter.command
|
||||
def publish():
|
||||
if checkpoint is not None:
|
||||
try: checkpoint()
|
||||
except OSError: result['receipt_checkpoint_failed'] = True
|
||||
def mark(stage):
|
||||
require(stage in diagnostics.DRIVER_STAGES)
|
||||
result['driver_stage'] = stage
|
||||
publish()
|
||||
def event(argv):
|
||||
require(len(result.setdefault('rpc_trace', [])) < 192)
|
||||
operation = argv[1] if len(argv) > 1 and argv[1] in {'run','exec','rm','container','context','image','info','volume'} else 'other'
|
||||
row = {'stage': result['driver_stage'], 'operation': operation, 'status': 'started'}
|
||||
result['rpc_trace'].append(row)
|
||||
publish()
|
||||
return row
|
||||
def run(argv, **kwargs):
|
||||
row = event(argv)
|
||||
remaining = deadline - time.monotonic()
|
||||
require(remaining > 0)
|
||||
try:
|
||||
value = original_command(argv, timeout=min(50, remaining), **kwargs)
|
||||
row['status'] = 'completed'
|
||||
publish()
|
||||
return value
|
||||
except BaseException as error:
|
||||
row['status'] = 'failed'
|
||||
result['daemon_request_uncertain'] = True
|
||||
result.setdefault('driver_failure', diagnostics.error_metadata(error, result['driver_stage']))
|
||||
# Timeout output can contain complete public progress frames. Never
|
||||
# retain raw stdout/stderr or command/exception text.
|
||||
if result['driver_stage'] in manifest['phase_oracles']:
|
||||
partial = getattr(error, 'stdout', None)
|
||||
if partial:
|
||||
try:
|
||||
stages, _ = diagnostics.parse_frames(partial, require_final=False)
|
||||
result.setdefault('helper_diagnostics', {})[result['driver_stage']] = {'status': 'interrupted', 'stages': stages}
|
||||
except Exception:
|
||||
pass
|
||||
publish()
|
||||
raise
|
||||
mark('session_registered')
|
||||
adapter.command = run
|
||||
try:
|
||||
anchor = session['worker']
|
||||
args = base_args(session, anchor, 'none', user='999:999') + [
|
||||
'--tmpfs', '/var/lib/postgresql:rw,nosuid,nodev,noexec,size=1048576,uid=999,gid=999,mode=0700',
|
||||
'--tmpfs', '/var/lib/postgresql/data:rw,nosuid,nodev,noexec,size=134217728,uid=999,gid=999,mode=0700',
|
||||
'--tmpfs', '/run/fixture:rw,nosuid,nodev,noexec,size=1048576,uid=999,gid=999,mode=0700',
|
||||
'--env', 'PGDATA=/var/lib/postgresql/data', '--entrypoint', '/bin/sh',
|
||||
manifest['postgres_image'], '-c', 'sleep 240']
|
||||
mark('anchor_launch')
|
||||
run(args)
|
||||
mark('anchor_containment')
|
||||
adapter.inspect_session({**session, 'containers': [anchor], 'issuer': anchor}, worker_only=True)
|
||||
admin_password, runtime_password = secrets.token_hex(24), secrets.token_hex(24)
|
||||
mark('postgres_private_import')
|
||||
run(['docker', 'exec', '-i', anchor, 'tar', '-xf', '-', '-C', '/run/fixture'],
|
||||
input=tar_bytes({'password': admin_password.encode()}))
|
||||
mark('postgres_initdb')
|
||||
initialized = json.loads(run(['docker', 'exec', anchor, '/bin/sh', '-c',
|
||||
"if initdb -D /var/lib/postgresql/data --username=fixture_admin --auth-local=trust --auth-host=scram-sha-256 --pwfile=/run/fixture/password > /dev/null 2>&1; then echo '{\"postgres_initdb\":true}'; else echo '{\"postgres_initdb\":false}'; fi"], max_output=256))
|
||||
result['postgres_initdb_complete'] = initialized == {'postgres_initdb': True}
|
||||
publish()
|
||||
require(result['postgres_initdb_complete'])
|
||||
mark('postgres_launch')
|
||||
run(['docker', 'exec', '--detach', anchor, '/bin/sh', '-c',
|
||||
'exec postgres -D /var/lib/postgresql/data -c listen_addresses=127.0.0.1 -c unix_socket_directories=/run/fixture > /dev/null 2>&1'])
|
||||
mark('anchor_identity')
|
||||
anchor_id = run(['docker', 'container', 'inspect', anchor, '--format', '{{.Id}}']).decode().strip()
|
||||
require(bool(re.fullmatch(r'[a-f0-9]{64}', anchor_id)))
|
||||
args = base_args(session, session['issuer'], 'container:' + anchor_id) + [
|
||||
'--tmpfs', '/fixture:rw,nosuid,nodev,noexec,size=16777216,uid=1000,gid=1000,mode=0700',
|
||||
'--entrypoint', python, manifest['python_image'], '-c', 'import time;time.sleep(240)']
|
||||
mark('helper_launch')
|
||||
run(args)
|
||||
mark('full_containment')
|
||||
adapter.inspect_session(session, worker_only=True)
|
||||
result['containment_verified_before_generated_private_import'] = True
|
||||
mark('helper_interpreter')
|
||||
interpreter = json.loads(run(['docker', 'exec', session['issuer'], python, '-c', INTERPRETER_PROBE], max_output=4096))
|
||||
require(interpreter.get('helper_ready') is True and interpreter.get('executable') == python)
|
||||
require(isinstance(interpreter.get('psycopg_version'), str) and re.fullmatch(r'3\.[0-9]+\.[0-9]+(?:[a-z]+[0-9]+)?', interpreter['psycopg_version']))
|
||||
result['helper_interpreter_verified_before_candidate_import'] = True
|
||||
result['helper_psycopg_version'] = interpreter['psycopg_version']
|
||||
files = {'source/' + name: (HERE.parent / 'postimage/identity-provisioner' / name).read_bytes()
|
||||
for name in ('provisioner.py', 'group_provenance.py', 'group_provenance_postgres.py',
|
||||
'migrations/group_journal_postgres_v1.sql')}
|
||||
files['source/tests/test_service_groups.py'] = (HERE / 'test_service_groups.py').read_bytes()
|
||||
files.update({'probe.py': (HERE / 'probe.py').read_bytes(), 'diagnostic_protocol.py': (HERE / 'diagnostic_protocol.py').read_bytes(), 'private.json': json.dumps({
|
||||
'admin_password': admin_password, 'runtime_password': runtime_password}).encode()})
|
||||
mark('candidate_import')
|
||||
run(['docker', 'exec', '-i', session['issuer'], python, '-c', IMPORT],
|
||||
input=json.dumps({k: base64.b64encode(v).decode() for k, v in files.items()}).encode())
|
||||
result['checks'] = {}
|
||||
backup = None
|
||||
active_database = 'postgres'
|
||||
for phase in ('bootstrap', 'restart', 'concurrency', 'fork', 'lost-ack', 'restored'):
|
||||
if phase == 'restored':
|
||||
require(backup is not None)
|
||||
mark('database_restore')
|
||||
run(['docker', 'exec', '-i', anchor, 'pg_restore', '--host', '/run/fixture', '-U', 'fixture_admin',
|
||||
'--dbname', active_database, '--clean', '--if-exists', '--single-transaction', '--exit-on-error', '--no-owner'], input=backup)
|
||||
mark(phase)
|
||||
raw = run(['docker', 'exec', session['issuer'], python, '/fixture/probe.py', phase], max_output=32768)
|
||||
value = retain_helper_result(result, phase, raw, publish)
|
||||
expected = set(manifest['phase_oracles'][phase])
|
||||
require(value.get('status') == 'passed' and set(value.get('checks', {})) == expected)
|
||||
require(all(v is True for v in value['checks'].values()))
|
||||
result['checks'].update(value['checks'])
|
||||
result['psycopg_version'] = value['psycopg_version']
|
||||
require(value.get('active_database') in {'postgres', 'fixture_fork'})
|
||||
active_database = value['active_database']
|
||||
if phase == 'bootstrap':
|
||||
mark('database_dump')
|
||||
backup = run(['docker', 'exec', anchor, 'pg_dump', '--host', '/run/fixture', '-U', 'fixture_admin',
|
||||
'--dbname', 'postgres', '--format=custom', '--no-owner'], max_output=4194304)
|
||||
result['status'] = 'synthetic_postgres_journal_compatibility_passed'
|
||||
mark('complete')
|
||||
except BaseException as error:
|
||||
result.setdefault('driver_failure', diagnostics.error_metadata(error, result['driver_stage']))
|
||||
publish()
|
||||
raise
|
||||
finally:
|
||||
mark('cleanup')
|
||||
def cleanup_command(argv, **kwargs):
|
||||
row = event(argv)
|
||||
try:
|
||||
value = original_command(argv, timeout=15, **kwargs)
|
||||
row['status'] = 'completed'
|
||||
publish()
|
||||
return value
|
||||
except BaseException:
|
||||
result['daemon_request_uncertain'] = True
|
||||
row['status'] = 'failed'
|
||||
publish()
|
||||
raise
|
||||
adapter.command = cleanup_command
|
||||
try:
|
||||
adapter.cleanup(session, result)
|
||||
result['container_cleanup_verified'] = True
|
||||
except BaseException:
|
||||
result['status'] = 'failed'
|
||||
result['container_cleanup_verified'] = False
|
||||
adapter.command = original_command
|
||||
publish()
|
||||
if result.get('daemon_request_uncertain') or result.get('receipt_checkpoint_failed'):
|
||||
result['status'] = 'failed'
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--plan', action='store_true')
|
||||
parser.add_argument('--execute-reviewed-fixture', action='store_true')
|
||||
parser.add_argument('--expected-manifest-sha256')
|
||||
args = parser.parse_args()
|
||||
if not args.execute_reviewed_fixture:
|
||||
print(MANIFEST.read_text())
|
||||
return 0
|
||||
require(not args.plan)
|
||||
manifest = reviewed_manifest(args.expected_manifest_sha256)
|
||||
adapter = load_containment(manifest)
|
||||
preflight(adapter, manifest)
|
||||
os.umask(0o077)
|
||||
output = Path(tempfile.mkdtemp(prefix='nk48-postgres-native-review-', dir='/tmp')) / 'result.json'
|
||||
result = {'schema': 'nk48.synthetic-postgres-journal-result.v1', 'status': 'failed',
|
||||
'manifest_sha256': args.expected_manifest_sha256, 'production_contacted': False,
|
||||
'actual_credentials_used': False, 'native_acceptance_claimed': False}
|
||||
try:
|
||||
execute(manifest, adapter, result, checkpoint=lambda: output.write_text(json.dumps(result, indent=2) + '\n'))
|
||||
except BaseException:
|
||||
result['status'] = 'failed'
|
||||
finally:
|
||||
output.write_text(json.dumps(result, indent=2) + '\n')
|
||||
print(json.dumps({'status': result['status'], 'safe_result': str(output)}))
|
||||
return int(result['status'] != 'synthetic_postgres_journal_compatibility_passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,300 @@
|
|||
{
|
||||
"schema": "nk48.synthetic-postgres-journal-result.v1",
|
||||
"status": "synthetic_postgres_journal_compatibility_passed",
|
||||
"manifest_sha256": "d51f90816616848b93bcdc482e10a98d07061deb01eeed9e50700fc6e68b6051",
|
||||
"production_contacted": false,
|
||||
"actual_credentials_used": false,
|
||||
"native_acceptance_claimed": false,
|
||||
"session": {
|
||||
"run_id": "492a7afaec6b43ef92b98eb76025362f",
|
||||
"containers": [
|
||||
"nk49-kc-492a7afaec6b43ef92b98eb76025362f-journal-postgres",
|
||||
"nk49-kc-492a7afaec6b43ef92b98eb76025362f-journal-probe"
|
||||
],
|
||||
"label": "net-kingdom.recovery-run=492a7afaec6b43ef92b98eb76025362f"
|
||||
},
|
||||
"driver_stage": "cleanup",
|
||||
"rpc_trace": [
|
||||
{
|
||||
"stage": "anchor_launch",
|
||||
"operation": "run",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "anchor_containment",
|
||||
"operation": "context",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "anchor_containment",
|
||||
"operation": "info",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "anchor_containment",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "anchor_containment",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "postgres_private_import",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "postgres_initdb",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "postgres_launch",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "anchor_identity",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "helper_launch",
|
||||
"operation": "run",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "full_containment",
|
||||
"operation": "context",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "full_containment",
|
||||
"operation": "info",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "full_containment",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "full_containment",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "full_containment",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "helper_interpreter",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "candidate_import",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "bootstrap",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "database_dump",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "restart",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "concurrency",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "fork",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "lost-ack",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "database_restore",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "restored",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "rm",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "rm",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
}
|
||||
],
|
||||
"postgres_initdb_complete": true,
|
||||
"containment_verified_before_generated_private_import": true,
|
||||
"helper_interpreter_verified_before_candidate_import": true,
|
||||
"helper_psycopg_version": "3.3.6",
|
||||
"checks": {
|
||||
"only_loopback_no_external_routes": true,
|
||||
"actual_postgresql_journal_ensure": true,
|
||||
"explicit_scope_context_retained": true,
|
||||
"confirmed_inventory_excludes_preexisting": true,
|
||||
"runtime_ddl_denied": true,
|
||||
"separate_process_postgres_restart_and_revoke": true,
|
||||
"preexisting_shared_state_preserved": true,
|
||||
"foreign_context_receipt_refused": true,
|
||||
"concurrent_original_reservation_exactly_once": true,
|
||||
"stale_version_update_refused_without_head_advance": true,
|
||||
"concurrent_divergent_clones_same_counter_verified": true,
|
||||
"independent_exact_transition_cas_selects_one_winner": true,
|
||||
"reopened_loser_clone_refused_at_same_counter": true,
|
||||
"lost_checkpoint_ack_retains_pending_reservation": true,
|
||||
"reopened_uncertainty_refuses_ldap_effects": true,
|
||||
"active_fence_survives_process_reopen": true,
|
||||
"lost_synthetic_directory_response_retains_postgres_intent": true,
|
||||
"lost_sql_commit_reply_retains_committed_head": true,
|
||||
"reopened_stale_checkpoint_refuses_continuation": true,
|
||||
"actual_pg_restore_behind_checkpoint_detected": true,
|
||||
"restored_store_cannot_gain_replay_authority": true
|
||||
},
|
||||
"helper_diagnostics": {
|
||||
"bootstrap": {
|
||||
"status": "passed",
|
||||
"stages": [
|
||||
"probe_entry",
|
||||
"loopback_check",
|
||||
"phase_dispatch",
|
||||
"bootstrap_connect_wait",
|
||||
"bootstrap_connected",
|
||||
"bootstrap_schema",
|
||||
"bootstrap_migration",
|
||||
"bootstrap_epoch_insert",
|
||||
"bootstrap_role_create",
|
||||
"bootstrap_runtime_grants",
|
||||
"bootstrap_checkpoint_init",
|
||||
"bootstrap_directory_init",
|
||||
"bootstrap_ensure",
|
||||
"bootstrap_ensure_readback",
|
||||
"bootstrap_runtime_ddl_refusal",
|
||||
"bootstrap_complete",
|
||||
"phase_complete"
|
||||
]
|
||||
},
|
||||
"restart": {
|
||||
"status": "passed",
|
||||
"stages": [
|
||||
"probe_entry",
|
||||
"loopback_check",
|
||||
"phase_dispatch",
|
||||
"restart_load_snapshot",
|
||||
"restart_reopen_store",
|
||||
"restart_replay_ensure",
|
||||
"restart_revoke",
|
||||
"restart_revoke_readback",
|
||||
"restart_prestate_equality",
|
||||
"restart_foreign_context",
|
||||
"restart_complete",
|
||||
"phase_complete"
|
||||
]
|
||||
},
|
||||
"concurrency": {
|
||||
"status": "passed",
|
||||
"stages": [
|
||||
"probe_entry",
|
||||
"loopback_check",
|
||||
"phase_dispatch",
|
||||
"phase_complete"
|
||||
]
|
||||
},
|
||||
"fork": {
|
||||
"status": "passed",
|
||||
"stages": [
|
||||
"probe_entry",
|
||||
"loopback_check",
|
||||
"phase_dispatch",
|
||||
"phase_complete"
|
||||
]
|
||||
},
|
||||
"lost-ack": {
|
||||
"status": "passed",
|
||||
"stages": [
|
||||
"probe_entry",
|
||||
"loopback_check",
|
||||
"phase_dispatch",
|
||||
"phase_complete"
|
||||
]
|
||||
},
|
||||
"restored": {
|
||||
"status": "passed",
|
||||
"stages": [
|
||||
"probe_entry",
|
||||
"loopback_check",
|
||||
"phase_dispatch",
|
||||
"phase_complete"
|
||||
]
|
||||
}
|
||||
},
|
||||
"psycopg_version": "3.3.6",
|
||||
"container_cleanup_obligations": [
|
||||
{
|
||||
"container": "nk49-kc-492a7afaec6b43ef92b98eb76025362f-journal-probe",
|
||||
"status": "absence_verified"
|
||||
},
|
||||
{
|
||||
"container": "nk49-kc-492a7afaec6b43ef92b98eb76025362f-journal-postgres",
|
||||
"status": "absence_verified"
|
||||
}
|
||||
],
|
||||
"container_cleanup_verified": true
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"status": "fixture_helper_successor_proposed_not_executed",
|
||||
"selected_cached_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b",
|
||||
"producer_revision": "1bb7cacc98479f56f6b1c28c000479efd260a944",
|
||||
"producer_containerfile_sha256": "668a90308bb6307f95a8c5439e5d751f81bf1f7126755e26373f3523f07c858a",
|
||||
"producer_containerfile_public_source": "https://forgejo.coulomb.social/coulomb/user-engine/src/commit/1bb7cacc98479f56f6b1c28c000479efd260a944/Containerfile",
|
||||
"provenance_sources": [
|
||||
{
|
||||
"repo": "rapp-user-engine",
|
||||
"path": "docs/evidence/2026-10-11-published-readback-proposal/published-receipt.json",
|
||||
"revision": "a42c68f8701880bfeb93e28dd812fcf3691568a3",
|
||||
"sha256": "2f84959e36d442860fb2b5a5817bb707c4f8372797a3ef8dd0d5cab956af86ce",
|
||||
"public_source": "https://forgejo.coulomb.social/coulomb/rapp-user-engine/src/commit/a42c68f8701880bfeb93e28dd812fcf3691568a3/docs/evidence/2026-10-11-published-readback-proposal/published-receipt.json"
|
||||
},
|
||||
{
|
||||
"repo": "user-engine",
|
||||
"path": "docs/evidence/2026-10-11-one-app-catalogue-executor/isolated-postgres-receipt.json",
|
||||
"revision": "9557d6f867fe03c546aa26655f84b4192109981d",
|
||||
"sha256": "964cc3755e96330bf6d1ada63410f8adfdb3c45602e0446369d6dddace412405",
|
||||
"public_source": "https://forgejo.coulomb.social/coulomb/user-engine/src/commit/9557d6f867fe03c546aa26655f84b4192109981d/docs/evidence/2026-10-11-one-app-catalogue-executor/isolated-postgres-receipt.json"
|
||||
}
|
||||
],
|
||||
"selection_reason": "coordinator reports exact image cached; published source receipt binds image to producer; prior owner fixture used exact image for psycopg dependencies",
|
||||
"interpreter": "/usr/local/bin/python3",
|
||||
"interpreter_binding": "producer officialPython3.12+systempip runtime, no virtualenv; execution must verify exact sys.executable/psycopg3 before candidate import, no PATHfallback",
|
||||
"not_claimed": [
|
||||
"worker did not query Docker or create/run containers",
|
||||
"bundled psycopg version not observed yet; must be reported by exact helper",
|
||||
"historical image risk/production acceptance not transferred",
|
||||
"cached helper is not a future identity-provisioner image/dependency admission"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"from_manifest_sha256": "4a816f74f857dfbebb5eaa0de3d792c91cd5e0f52caf458c2f7789583507ee31",
|
||||
"to_manifest_sha256": "8ac8547b43ac1679acadd422993bc3fbce842a6c315a728af4d452734910240b",
|
||||
"source_patch_unchanged": "503e3d0217e6be25df8a58f3968cab7c00cdd9933d0fee2a66ef951fc4aae2b1",
|
||||
"helper_image": {
|
||||
"before": "forgejo.coulomb.social/coulomb/user-engine@sha256:1e6547c40c93c49f01438edcf76c20f561ccebc110220bae020723505aa56c70",
|
||||
"after": "forgejo.coulomb.social/coulomb/user-engine@sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b"
|
||||
},
|
||||
"helper_interpreter": {
|
||||
"before": "PATH python3",
|
||||
"after": "/usr/local/bin/python3",
|
||||
"verification": "closed exact sys.executable/psycopg3 probe before candidate source import"
|
||||
},
|
||||
"unchanged_contracts": {
|
||||
"postgres_image": true,
|
||||
"fixed_endpoint": true,
|
||||
"engine_id": true,
|
||||
"engine_name": true,
|
||||
"limits": true,
|
||||
"resources": true,
|
||||
"phase_oracles": true,
|
||||
"cleanup": true,
|
||||
"network": true,
|
||||
"pull_policy": true
|
||||
},
|
||||
"worker_docker_calls": false,
|
||||
"execution_requires_new_independent_review": true
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"schema": "nk48.fixture-manifest-delta.v1",
|
||||
"previous_manifest_sha256": "8ac8547b43ac1679acadd422993bc3fbce842a6c315a728af4d452734910240b",
|
||||
"successor_manifest_sha256": "78f2b0bbd7639780ca3a3a689310527f31e7e14c1fdb497a48bac4f69bce4093",
|
||||
"changed_top_level_fields": [
|
||||
"diagnostics",
|
||||
"r2_outcome",
|
||||
"revision",
|
||||
"source_sha256",
|
||||
"supersedes_manifest_sha256"
|
||||
],
|
||||
"unchanged_contract_fields": [
|
||||
"candidate_patch_sha256",
|
||||
"containment_helper",
|
||||
"fixed_endpoint",
|
||||
"engine_id",
|
||||
"engine_name",
|
||||
"postgres_image",
|
||||
"python_image",
|
||||
"helper_interpreter",
|
||||
"limits",
|
||||
"resources",
|
||||
"phase_oracles",
|
||||
"cleanup",
|
||||
"network",
|
||||
"pull_policy"
|
||||
],
|
||||
"runtime_executed": false
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"schema": "nk48.fixture-manifest-delta.v1",
|
||||
"previous_manifest_sha256": "78f2b0bbd7639780ca3a3a689310527f31e7e14c1fdb497a48bac4f69bce4093",
|
||||
"successor_manifest_sha256": "d862780e2954d7103a32294ff67e1bfe5e70b5f33bf66ffe73b8521a76bb362d",
|
||||
"changed_top_level_fields": [
|
||||
"candidate_patch_sha256",
|
||||
"r3_outcome",
|
||||
"r4_refinement",
|
||||
"revision",
|
||||
"source_sha256",
|
||||
"supersedes_manifest_sha256"
|
||||
],
|
||||
"unchanged_contract_fields": [
|
||||
"containment_helper",
|
||||
"fixed_endpoint",
|
||||
"engine_id",
|
||||
"engine_name",
|
||||
"postgres_image",
|
||||
"python_image",
|
||||
"helper_interpreter",
|
||||
"limits",
|
||||
"resources",
|
||||
"phase_oracles",
|
||||
"cleanup",
|
||||
"network",
|
||||
"pull_policy",
|
||||
"diagnostics"
|
||||
],
|
||||
"driver_probe_unchanged": true,
|
||||
"runtime_executed": false
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"previous_manifest_sha256": "d862780e2954d7103a32294ff67e1bfe5e70b5f33bf66ffe73b8521a76bb362d",
|
||||
"successor_manifest_sha256": "d51f90816616848b93bcdc482e10a98d07061deb01eeed9e50700fc6e68b6051",
|
||||
"changed_top_level_fields": [
|
||||
"r4_outcome",
|
||||
"r5_refinement",
|
||||
"revision",
|
||||
"source_sha256",
|
||||
"supersedes_manifest_sha256"
|
||||
],
|
||||
"unchanged_contract_fields": [
|
||||
"candidate_patch_sha256",
|
||||
"containment_helper",
|
||||
"fixed_endpoint",
|
||||
"engine_id",
|
||||
"engine_name",
|
||||
"postgres_image",
|
||||
"python_image",
|
||||
"helper_interpreter",
|
||||
"limits",
|
||||
"resources",
|
||||
"phase_oracles",
|
||||
"cleanup",
|
||||
"network",
|
||||
"pull_policy"
|
||||
],
|
||||
"source_backend_unchanged": true,
|
||||
"driver_unchanged": true,
|
||||
"runtime_executed": false
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
{
|
||||
"schema": "nk48.postgres-journal-fixture-proposal.v1",
|
||||
"status": "prepared_not_executed_not_approved",
|
||||
"source_sha256": {
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py": "sha256:04237939df1887df8a201181d87ebfacfa25855401b64f4998a3921111bd91eb",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/probe.py": "sha256:370f569d75d657e030bf27f8b127b9f5e3ca49c4efa91e8d53a02ade35bdff78",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_driver.py": "sha256:ab735b251ca70c709035b62d501dd46f842792fcb2c3b69888ae2aad698cff6e",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_service_groups.py": "sha256:815571fd92c0001dacbe250640acd99b2f2872b4171519ed54f8f469ee90aafe",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/source-manifest.json": "sha256:89456d4c18510153a1aae61bcbc1163f4d2f28df6de29d8801ba49881a84f515",
|
||||
"/home/worsch/net-kingdom/tools/bootstrap-recovery/isolated-keycape.py": "sha256:f67627b7d1647d75187dd899874efe22383bc91de5d97dabb9801076668ab3c2",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/provisioner.py": "sha256:19d1b837cbafeeacb1aa815856cd68091e6702281d72b1d860230fa320e1725b",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/group_provenance.py": "sha256:405e08eaaab7c21d618dff4db5970607806f32ab6c461effb81c737e32182b10",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/group_provenance_postgres.py": "sha256:2a0111e83ad7b611076a1b45634e7f698b3d2e9acc473320b4a8ba009ebd0932",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/migrations/group_journal_postgres_v1.sql": "sha256:3cdf82194d6f1e2dd865b0c7e7b8a14447fda8c49151148134a0d40a80fb46ac",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/README.md": "sha256:e50dcc7002cc21c8f7f971878df4afc0c3516d281fe0ecb9a14600aacac56fb4",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/README.md": "sha256:cf31dd40ece9991c66fe8192e88db6e7714d260eac0b94403849b9825d763c7c",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/helper-selection-r2.json": "sha256:ec5464c0d5310028ab91073f985183af01da2d194358fad412d197dbf90106d9",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/producer-Containerfile.txt": "sha256:668a90308bb6307f95a8c5439e5d751f81bf1f7126755e26373f3523f07c858a",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/diagnostic_protocol.py": "sha256:99330dddba4ba801675a65eff3c43e402fe34b791b2aa8fed64c75fc43fd0b9d",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_snapshot.py": "sha256:67241095e55b857f081432517f35902cbb9e96204cc7fa1002142acd4361f37c",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/revisions/r4/probe.py": "sha256:39eea88a246a799945da72d022200dd9e15662b765bbc896a4edbdc2f8bfd5a0"
|
||||
},
|
||||
"candidate_patch_sha256": "5549d2982b61ff70aba4b3928fecd5639db0f1a732ab2f8630f678e7acf4d34d",
|
||||
"containment_helper": "/home/worsch/net-kingdom/tools/bootstrap-recovery/isolated-keycape.py",
|
||||
"fixed_endpoint": "unix:///var/run/docker.sock",
|
||||
"engine_id": "74f93783-6cef-43a8-a3c4-f62586cd3bc1",
|
||||
"engine_name": "bnt-lap001",
|
||||
"postgres_image": "postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70",
|
||||
"python_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b",
|
||||
"image_pin_origin": "PostgreSQL pin unchanged; cached f239 helper selected from coordinator inventory and tied to UserEngine producer1bb7cacc by existing published receipt/previous owner PostgreSQL fixture; fixture-only selection, no future image admission",
|
||||
"pull_policy": "never",
|
||||
"network": "network-none",
|
||||
"scope": "real disposable PostgreSQL backend SQL/migration/concurrency/restore compatibility; LDAP is explicitly the existing synthetic test Directory, not actual native LDAP in this fixture",
|
||||
"limits": {
|
||||
"max_service_containers": 2,
|
||||
"host_ports": 0,
|
||||
"host_mounts": 0,
|
||||
"named_or_anonymous_volumes": 0,
|
||||
"memory_bytes_per_container": 402653184,
|
||||
"cpus_per_container": 0.5,
|
||||
"pids_per_container": 64,
|
||||
"postgres_tmpfs_bytes": 153092096,
|
||||
"helper_tmpfs_bytes": 33554432,
|
||||
"backup_in_host_memory_max_bytes": 4194304,
|
||||
"container_lifetime_seconds": 240,
|
||||
"execution_deadline_seconds": 210,
|
||||
"docker_rpc_timeout_seconds": 50,
|
||||
"preflight_rpc_timeout_seconds": 15,
|
||||
"cleanup_rpc_timeout_seconds": 15,
|
||||
"cleanup_total_bound_seconds": 120,
|
||||
"new_cloud_spend_eur": 0
|
||||
},
|
||||
"compatibility_preconditions": [
|
||||
"exact cached image/engine pins reverified by admitted preflight, never pulled",
|
||||
"PostgreSQL image supports nonroot UID999 with initdb/postgres/pg_dump/pg_restore in PATH; failed compatibility refuses/cleans",
|
||||
"User Engine f239 producer source uses official Python system prefix; exact /usr/local/bin/python3 and bundled psycopg3 must pass closed probe before candidate import, no PATH/venv fallback or downloads",
|
||||
"all image-declared volumes must be masked by private tmpfs; existing containment guard refuses any anonymous volume before private import"
|
||||
],
|
||||
"resources": {
|
||||
"container_names": "nk49-kc-<fresh32hex>-journal-{postgres,probe}",
|
||||
"exact_label": "net-kingdom.recovery-run=<same-fresh32hex>",
|
||||
"private_state": "generated admin/runtime passwords, source/migration, synthetic directory, external test checkpoint within private container tmpfs",
|
||||
"safe_result": "private /tmp/nk48-postgres-native-review-<suffix>/result.json"
|
||||
},
|
||||
"phase_oracles": {
|
||||
"bootstrap": [
|
||||
"only_loopback_no_external_routes",
|
||||
"actual_postgresql_journal_ensure",
|
||||
"explicit_scope_context_retained",
|
||||
"confirmed_inventory_excludes_preexisting",
|
||||
"runtime_ddl_denied"
|
||||
],
|
||||
"restart": [
|
||||
"only_loopback_no_external_routes",
|
||||
"separate_process_postgres_restart_and_revoke",
|
||||
"preexisting_shared_state_preserved",
|
||||
"foreign_context_receipt_refused"
|
||||
],
|
||||
"concurrency": [
|
||||
"only_loopback_no_external_routes",
|
||||
"concurrent_original_reservation_exactly_once",
|
||||
"stale_version_update_refused_without_head_advance"
|
||||
],
|
||||
"lost-ack": [
|
||||
"only_loopback_no_external_routes",
|
||||
"lost_checkpoint_ack_retains_pending_reservation",
|
||||
"reopened_uncertainty_refuses_ldap_effects",
|
||||
"active_fence_survives_process_reopen",
|
||||
"lost_synthetic_directory_response_retains_postgres_intent",
|
||||
"lost_sql_commit_reply_retains_committed_head",
|
||||
"reopened_stale_checkpoint_refuses_continuation"
|
||||
],
|
||||
"restored": [
|
||||
"only_loopback_no_external_routes",
|
||||
"actual_pg_restore_behind_checkpoint_detected",
|
||||
"restored_store_cannot_gain_replay_authority"
|
||||
],
|
||||
"fork": [
|
||||
"only_loopback_no_external_routes",
|
||||
"concurrent_divergent_clones_same_counter_verified",
|
||||
"independent_exact_transition_cas_selects_one_winner",
|
||||
"reopened_loser_clone_refused_at_same_counter"
|
||||
]
|
||||
},
|
||||
"restore": "actual generated pg_dump after bootstrap; winner database advances through reopen/concurrency/fork/uncertainty; actual pg_restore of original archive while independent content checkpoint survives; runtime SELECT must work and show older restored head before refusal can pass",
|
||||
"cleanup": "finally reuses exact name/label cleanup for both preregistered containers, including failed/lost-create replies; successful explicit absence required; any Docker uncertainty remains failed; no prune/global removal",
|
||||
"not_claimed": [
|
||||
"CNPG HA, native runtime storage/backup/restore admission",
|
||||
"authoritative tenant/case/PDP/evidence/session owner callbacks",
|
||||
"external LDAP writer fencing/CAS/ABA safety",
|
||||
"production endpoint activation, credentials or owner assent",
|
||||
"KeyCape hard-parent-loss reaper proof"
|
||||
],
|
||||
"forbidden": [
|
||||
"Docker commands before exact independent review",
|
||||
"pull/build/push, host networking/ports/mounts, production APIs",
|
||||
"real database/LDAP, identity archives, credentials from environment/custody",
|
||||
"clearing production restore quarantine or manufacturing scope/authority"
|
||||
],
|
||||
"execution_command": [
|
||||
"python3",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py",
|
||||
"--execute-reviewed-fixture",
|
||||
"--expected-manifest-sha256",
|
||||
"<reviewed-exact-manifest-digest>"
|
||||
],
|
||||
"fork": "actual PostgreSQL CREATE DATABASE TEMPLATE clone; both stores pass exact same old external marker check under test barrier, commit different bodies at same counter, race external strict CAS; exactly one wins and fresh loser store refuses; no directory effect in this direct reservation case",
|
||||
"revision": "r5-detached-current-directory-snapshot",
|
||||
"helper_interpreter": "/usr/local/bin/python3",
|
||||
"supersedes_manifest_sha256": "d862780e2954d7103a32294ff67e1bfe5e70b5f33bf66ffe73b8521a76bb362d",
|
||||
"r1_preflight": "failed missing helper before execute/session/private import; preserved structured failure, no raw stderr file exists",
|
||||
"r2_outcome": "failed after containment/interpreter verification; checks empty, exact helper subphase unavailable; byte-exact safe receipt retained revisions/r2/execution-result.json; cleanup absence verified",
|
||||
"diagnostics": {
|
||||
"driver_stage": "fixed closed vocabulary before each RPC/bootstep, checkpointed result",
|
||||
"probe_stage": "fixed closed vocabulary NDJSON before bootstrap substeps, failed final retained before oracle refusal",
|
||||
"exceptions": "allowlisted class and optional five-character SQLSTATE only, never values/logs/exception messages",
|
||||
"max_probe_output_bytes": 32768,
|
||||
"max_probe_frames": 96,
|
||||
"max_rpc_trace_rows": 192,
|
||||
"initdb": "synchronous fixed boolean completion before detached server launch"
|
||||
},
|
||||
"r3_outcome": "failed safely at bootstrap_ensure RuntimeError/noSQLSTATE before readback; earlier boot/migration/grants complete; source nested reference replacement defect independently reproduced locally; frozen source and safe receipt retained",
|
||||
"r4_refinement": "Postgres.save updates only acknowledged backend version, preserving caller nested references; two local regressions; source82 tests pass; phase oracles unchanged",
|
||||
"r4_outcome": "all five bootstrap oracles passed actual SQL; restart RuntimeError before fine-grained markers; safe receipt/full fixture/source retained revisions/r4 and review-r4",
|
||||
"r5_refinement": "snapshot copies mutable group mapping, checks all current IDs and full memberships, refuses nonempty orphan backing sets; two local actual Directory/provisioner tests demonstrate old defects and preservation; fixed restart markers; oracle names unchanged"
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"status": "prepared_review_required_not_executed",
|
||||
"fixture_plan_command": "python3 -m pytest -q docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_driver.py",
|
||||
"fixture_plan_result": "9 passed in0.05s",
|
||||
"source_backend_unchanged": true,
|
||||
"source_patch_sha256": "503e3d0217e6be25df8a58f3968cab7c00cdd9933d0fee2a66ef951fc4aae2b1",
|
||||
"r2_manifest_sha256": "8ac8547b43ac1679acadd422993bc3fbce842a6c315a728af4d452734910240b",
|
||||
"r1_manifest_sha256": "4a816f74f857dfbebb5eaa0de3d792c91cd5e0f52caf458c2f7789583507ee31",
|
||||
"worker_docker_executed": false,
|
||||
"root_reported_r1_preflight": "missing helper, failed before execute/session/private import; structured original retained, rawstderrfile absent",
|
||||
"native_success_claimed": false,
|
||||
"producer_history_is_not_future_image_admission": true
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"schema": "nk48.fixture-preparation.v1",
|
||||
"revision": "r3-bounded-stage-diagnostics",
|
||||
"status": "prepared_not_executed_not_approved",
|
||||
"manifest_sha256": "78f2b0bbd7639780ca3a3a689310527f31e7e14c1fdb497a48bac4f69bce4093",
|
||||
"candidate_patch_sha256": "503e3d0217e6be25df8a58f3968cab7c00cdd9933d0fee2a66ef951fc4aae2b1",
|
||||
"unit_check": {
|
||||
"command": "python3 -m unittest discover -s docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture -p test_driver.py",
|
||||
"tests": 13,
|
||||
"passed": true,
|
||||
"seconds": 0.015
|
||||
},
|
||||
"previous_failure": {
|
||||
"safe_receipt": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/revisions/r2/execution-result.json",
|
||||
"sha256": "fb134a5f0444f7aabb6ea8f48f7e9bcdeb028bf42faecd477d0eeee882f14d3c",
|
||||
"exact_helper_failure_subphase": "unknown_not_retrospectively_assigned",
|
||||
"helper_psycopg_version": "3.3.6",
|
||||
"cleanup_absence_verified": true
|
||||
},
|
||||
"execution_command": [
|
||||
"python3",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py",
|
||||
"--execute-reviewed-fixture",
|
||||
"--expected-manifest-sha256",
|
||||
"78f2b0bbd7639780ca3a3a689310527f31e7e14c1fdb497a48bac4f69bce4093"
|
||||
],
|
||||
"source_backend_changed": false,
|
||||
"docker_commands_executed_in_preparation": false,
|
||||
"independent_review_required": true
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"status": "prepared_not_executed_not_approved",
|
||||
"manifest_sha256": "d862780e2954d7103a32294ff67e1bfe5e70b5f33bf66ffe73b8521a76bb362d",
|
||||
"candidate_patch_sha256": "5549d2982b61ff70aba4b3928fecd5639db0f1a732ab2f8630f678e7acf4d34d",
|
||||
"source_tests": "82 passed in 8.46s",
|
||||
"patch_apply_check": true,
|
||||
"regression_before_fix": "FAIL test_save_preserves_nested_receipt_references_held_by_provisioner; nested groups identity changed",
|
||||
"fix": "acknowledged version only after transaction+independent checkpoint, preserve nested references",
|
||||
"runtime_compatibility": "pending exact independent review/execution",
|
||||
"execution_command": [
|
||||
"python3",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py",
|
||||
"--execute-reviewed-fixture",
|
||||
"--expected-manifest-sha256",
|
||||
"d862780e2954d7103a32294ff67e1bfe5e70b5f33bf66ffe73b8521a76bb362d"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"status": "prepared_not_executed_not_approved",
|
||||
"manifest_sha256": "d51f90816616848b93bcdc482e10a98d07061deb01eeed9e50700fc6e68b6051",
|
||||
"candidate_patch_sha256": "5549d2982b61ff70aba4b3928fecd5639db0f1a732ab2f8630f678e7acf4d34d",
|
||||
"local_snapshot_tests": "2 passed in0.236s",
|
||||
"snapshot_test_command": [
|
||||
"python3",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_snapshot.py"
|
||||
],
|
||||
"source_backend_unchanged": true,
|
||||
"frozen_r4_receipt_sha256": "540a4080f57514fde4e07c2fab888456e4d6fbe7b22d016fe4bcce7bbc35e715",
|
||||
"runtime_compatibility": "bootstrap5passed; remaining phases pending exact successor review/execution",
|
||||
"execution_command": [
|
||||
"python3",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py",
|
||||
"--execute-reviewed-fixture",
|
||||
"--expected-manifest-sha256",
|
||||
"d51f90816616848b93bcdc482e10a98d07061deb01eeed9e50700fc6e68b6051"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,335 @@
|
|||
"""Synthetic PostgreSQL journal compatibility; LDAP remains a declared test double."""
|
||||
import concurrent.futures
|
||||
from dataclasses import replace
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import uuid
|
||||
|
||||
ROOT = Path('/fixture')
|
||||
sys.path[:0] = [str(ROOT / 'source'), str(ROOT / 'source/tests')]
|
||||
from diagnostic_protocol import emit_stage, error_metadata
|
||||
CURRENT_STAGE = 'probe_entry'
|
||||
|
||||
def stage(value):
|
||||
global CURRENT_STAGE
|
||||
CURRENT_STAGE = value
|
||||
emit_stage(value)
|
||||
|
||||
stage('probe_entry')
|
||||
try:
|
||||
import psycopg
|
||||
from group_provenance import GroupProvenanceUnavailable
|
||||
from group_provenance_postgres import GroupJournalBinding, PostgresGroupProvenance, psycopg_connector, journal_genesis
|
||||
from test_service_groups import Directory, req
|
||||
|
||||
EPOCH = 'bfbb8939-e88c-4f9c-a5c6-14ba3e399ddf'
|
||||
CONTEXT = GroupJournalBinding('tenant:synthetic:one', 'synthetic-pg-directory', 'sha256:' + '1' * 64,
|
||||
'sha256:' + '2' * 64, 'synthetic-principal', EPOCH)
|
||||
except Exception as error:
|
||||
print(json.dumps({'status': 'failed', 'failure': 'synthetic_pg_oracle_failed',
|
||||
'diagnostic': error_metadata(error, CURRENT_STAGE)}), flush=True)
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
def require(value):
|
||||
if not value:
|
||||
raise RuntimeError('postgres_fixture_oracle_failed')
|
||||
|
||||
|
||||
def info(admin=False, database=None):
|
||||
values = json.loads((ROOT / 'private.json').read_text())
|
||||
database = database or ((ROOT / 'active-database').read_text() if (ROOT / 'active-database').exists() else 'postgres')
|
||||
require(database in {'postgres', 'fixture_fork'})
|
||||
return {'host': '127.0.0.1', 'port': 5432, 'dbname': database, 'user': 'fixture_admin' if admin else 'fixture_app',
|
||||
'password': values['admin_password'] if admin else values['runtime_password'], 'sslmode': 'disable'}
|
||||
|
||||
|
||||
class FileCheckpoint:
|
||||
"""Fixture-only independently retained checkpoint; never a native owner grant."""
|
||||
def __init__(self):
|
||||
self.path = ROOT / 'checkpoint.json'
|
||||
def _apply(self, epoch, directory, previous, previous_digest, current=None, current_digest=None):
|
||||
with (ROOT / 'checkpoint.lock').open('a+') as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
value = json.loads(self.path.read_text())
|
||||
require(value == {'epoch': epoch, 'directory': directory, 'head': previous, 'digest': previous_digest})
|
||||
if current is not None:
|
||||
temporary = ROOT / ('checkpoint-' + uuid.uuid4().hex)
|
||||
with temporary.open('x') as out:
|
||||
json.dump({'epoch': epoch, 'directory': directory, 'head': current, 'digest': current_digest}, out)
|
||||
out.flush(); os.fsync(out.fileno())
|
||||
os.replace(temporary, self.path)
|
||||
fd = os.open(ROOT, os.O_RDONLY | os.O_DIRECTORY)
|
||||
try: os.fsync(fd)
|
||||
finally: os.close(fd)
|
||||
return True
|
||||
def check(self, epoch, directory, head, digest):
|
||||
return self._apply(epoch, directory, head, digest)
|
||||
def advance(self, epoch, directory, previous, previous_digest, current, current_digest):
|
||||
return self._apply(epoch, directory, previous, previous_digest, current, current_digest)
|
||||
|
||||
|
||||
def store(context=CONTEXT, checkpoint=None, database=None):
|
||||
return PostgresGroupProvenance(connect=psycopg_connector(connection_info=lambda: info(database=database)), binding=context,
|
||||
continuity=checkpoint or FileCheckpoint())
|
||||
|
||||
|
||||
def refused(call):
|
||||
try: call()
|
||||
except (ValueError, RuntimeError): return
|
||||
raise RuntimeError('postgres_fixture_expected_refusal_missing')
|
||||
|
||||
|
||||
def snapshot(directory):
|
||||
# Directory._gql(deleteGroup) removes the visible group but retains its
|
||||
# empty backing set. Reject any nonempty orphan; compare every current
|
||||
# group and its complete membership. Copy mappings so later mutations
|
||||
# cannot change the recorded prestate in memory before serialization.
|
||||
current = set(directory.groups.values())
|
||||
require(all(not users for gid, users in directory.members.items() if gid not in current))
|
||||
return {'users': sorted(directory.users), 'groups': dict(directory.groups),
|
||||
'members': {str(gid): sorted(directory.members[gid]) for gid in current}}
|
||||
|
||||
|
||||
def restore_directory(value):
|
||||
directory = Directory()
|
||||
directory.users = set(value['users'])
|
||||
directory.groups = value['groups']
|
||||
directory.members = {int(k): set(v) for k, v in value['members'].items()}
|
||||
directory.group_provenance = store()
|
||||
return directory
|
||||
|
||||
|
||||
def bootstrap():
|
||||
stage('bootstrap_connect_wait')
|
||||
last_connection_error = None
|
||||
for _ in range(80):
|
||||
try:
|
||||
db = psycopg.connect(**info(admin=True), connect_timeout=1)
|
||||
db.close(); break
|
||||
except Exception as error:
|
||||
last_connection_error = error
|
||||
time.sleep(.25)
|
||||
else: raise RuntimeError('synthetic_postgres_not_ready') from last_connection_error
|
||||
stage('bootstrap_connected')
|
||||
from psycopg import sql
|
||||
with psycopg.connect(**info(admin=True)) as db:
|
||||
stage('bootstrap_schema')
|
||||
db.execute('CREATE SCHEMA identity_provisioner')
|
||||
stage('bootstrap_migration')
|
||||
db.execute((ROOT / 'source/migrations/group_journal_postgres_v1.sql').read_text())
|
||||
stage('bootstrap_epoch_insert')
|
||||
db.execute('INSERT INTO identity_provisioner.group_journal_meta VALUES (true, %s, %s, 0, %s, false)', (EPOCH, CONTEXT.directory, journal_genesis(EPOCH, CONTEXT.directory)))
|
||||
stage('bootstrap_role_create')
|
||||
db.execute(sql.SQL('CREATE ROLE fixture_app LOGIN PASSWORD {}').format(sql.Literal(info()['password'])))
|
||||
stage('bootstrap_runtime_grants')
|
||||
db.execute('GRANT USAGE ON SCHEMA identity_provisioner TO fixture_app')
|
||||
db.execute('GRANT SELECT, INSERT, UPDATE ON identity_provisioner.group_receipts TO fixture_app')
|
||||
db.execute('GRANT SELECT ON identity_provisioner.group_journal_meta TO fixture_app')
|
||||
db.execute('GRANT UPDATE (head,head_digest) ON identity_provisioner.group_journal_meta TO fixture_app')
|
||||
stage('bootstrap_checkpoint_init')
|
||||
(ROOT / 'checkpoint.json').write_text(json.dumps({'epoch': EPOCH, 'directory': CONTEXT.directory, 'head': 0, 'digest': journal_genesis(EPOCH, CONTEXT.directory)}))
|
||||
stage('bootstrap_directory_init')
|
||||
directory = Directory()
|
||||
directory.members[3].add('tegwick')
|
||||
before = snapshot(directory)
|
||||
directory.group_provenance = store()
|
||||
stage('bootstrap_ensure')
|
||||
result = directory.ensure_service_groups(req())
|
||||
stage('bootstrap_ensure_readback')
|
||||
require(result.status == 'groups_ensured')
|
||||
require({x['name'] for x in result.created_groups} == {'forge-coulomb-owners', 'forge-coulomb-readers'})
|
||||
require({(x['name'], x['username']) for x in result.created_memberships} == {('forge-coulomb-owners', 'tegwick')})
|
||||
(ROOT / 'directory.json').write_text(json.dumps({'before': before, 'after': snapshot(directory)}))
|
||||
stage('bootstrap_runtime_ddl_refusal')
|
||||
with psycopg.connect(**info()) as db:
|
||||
try: db.execute('CREATE TABLE identity_provisioner.runtime_ddl_must_fail (id integer)')
|
||||
except psycopg.errors.InsufficientPrivilege: pass
|
||||
else: raise RuntimeError('runtime_ddl_not_denied')
|
||||
stage('bootstrap_complete')
|
||||
return {'actual_postgresql_journal_ensure': True, 'explicit_scope_context_retained': True,
|
||||
'confirmed_inventory_excludes_preexisting': True, 'runtime_ddl_denied': True}
|
||||
|
||||
|
||||
def restart():
|
||||
stage('restart_load_snapshot')
|
||||
saved = json.loads((ROOT / 'directory.json').read_text())
|
||||
stage('restart_reopen_store')
|
||||
directory = restore_directory(saved['after'])
|
||||
stage('restart_replay_ensure')
|
||||
require(directory.ensure_service_groups(req()).resumed)
|
||||
require(not directory.calls)
|
||||
stage('restart_revoke')
|
||||
result = directory.revoke_service_groups(req(ensure_idempotency_key=req()['idempotency_key'],
|
||||
ensure_correlation_id=req()['correlation_id'], delete_if_empty=True))
|
||||
stage('restart_revoke_readback')
|
||||
require(result.status == 'groups_revoked_and_deleted')
|
||||
stage('restart_prestate_equality')
|
||||
require(snapshot(directory) == saved['before'])
|
||||
stage('restart_foreign_context')
|
||||
foreign = store(replace(CONTEXT, principal='other-synthetic-principal'))
|
||||
refused(lambda: foreign.get(req()['idempotency_key'], directory._group_binding(req()['groups'], req()['members'], req()['correlation_id'])))
|
||||
stage('restart_complete')
|
||||
return {'separate_process_postgres_restart_and_revoke': True, 'preexisting_shared_state_preserved': True,
|
||||
'foreign_context_receipt_refused': True}
|
||||
|
||||
|
||||
def concurrency():
|
||||
binding = '4' * 64
|
||||
def reserve(_):
|
||||
try: return store().reserve('synthetic-concurrent-reservation', binding)
|
||||
except GroupProvenanceUnavailable: return None
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool:
|
||||
results = list(pool.map(reserve, range(24)))
|
||||
created = [r[0] for r in results if r is not None and r[1]]
|
||||
require(len(created) == 1)
|
||||
receipt = created[0]
|
||||
receipt['state'] = 'refused'
|
||||
store().save(receipt)
|
||||
stale = json.loads(json.dumps(receipt))
|
||||
store().save(receipt)
|
||||
head = json.loads((ROOT / 'checkpoint.json').read_text())['head']
|
||||
refused(lambda: store().save(stale))
|
||||
require(json.loads((ROOT / 'checkpoint.json').read_text())['head'] == head)
|
||||
with psycopg.connect(**info()) as db:
|
||||
require(db.execute('SELECT count(*) FROM identity_provisioner.group_receipts WHERE key=%s', ('synthetic-concurrent-reservation',)).fetchone()[0] == 1)
|
||||
return {'concurrent_original_reservation_exactly_once': True, 'stale_version_update_refused_without_head_advance': True}
|
||||
|
||||
|
||||
def fork():
|
||||
# Clone actual PostgreSQL state at the exact same head/digest. Both callers
|
||||
# must pass the old external check before either commits a divergent body.
|
||||
with psycopg.connect(**{**info(admin=True), 'dbname': 'template1'}, autocommit=True) as db:
|
||||
db.execute('CREATE DATABASE fixture_fork TEMPLATE postgres')
|
||||
barrier = threading.Barrier(2)
|
||||
class SimultaneousCheckpoint(FileCheckpoint):
|
||||
def check(self, *args):
|
||||
result = super().check(*args)
|
||||
barrier.wait(timeout=10)
|
||||
return result
|
||||
def attempt(database):
|
||||
candidate = store(database=database, checkpoint=SimultaneousCheckpoint())
|
||||
try:
|
||||
record, fresh = candidate.reserve('synthetic-fork-' + database, '8' * 64)
|
||||
require(fresh)
|
||||
return database, record
|
||||
except GroupProvenanceUnavailable:
|
||||
return database, None
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool:
|
||||
results = list(pool.map(attempt, ('postgres', 'fixture_fork')))
|
||||
winners = [(database, record) for database, record in results if record is not None]
|
||||
require(len(winners) == 1)
|
||||
winner, record = winners[0]
|
||||
loser = next(database for database, result in results if result is None)
|
||||
markers = {}
|
||||
for database in ('postgres', 'fixture_fork'):
|
||||
with psycopg.connect(**info(admin=True, database=database)) as db:
|
||||
markers[database] = db.execute('SELECT head,head_digest FROM identity_provisioner.group_journal_meta').fetchone()
|
||||
require(markers[winner][0] == markers[loser][0] and markers[winner][1] != markers[loser][1])
|
||||
store(database=winner).check()
|
||||
refused(lambda: store(database=loser).check())
|
||||
retained = json.loads((ROOT / 'checkpoint.json').read_text())
|
||||
require((retained['head'], retained['digest']) == markers[winner])
|
||||
# No directory call was attempted by this direct reservation fixture. Retain
|
||||
# terminal refusal for that key before continuing against the winner only.
|
||||
record['state'] = 'refused'
|
||||
store(database=winner).save(record)
|
||||
(ROOT / 'active-database').write_text(winner)
|
||||
return {'concurrent_divergent_clones_same_counter_verified': True,
|
||||
'independent_exact_transition_cas_selects_one_winner': True,
|
||||
'reopened_loser_clone_refused_at_same_counter': True}
|
||||
|
||||
|
||||
def restored():
|
||||
# Actual pg_restore has rolled SQL back while this checkpoint survived.
|
||||
with psycopg.connect(**info()) as db:
|
||||
epoch, directory, head, quarantine = db.execute('SELECT epoch::text,directory,head,quarantined FROM identity_provisioner.group_journal_meta').fetchone()
|
||||
require(epoch == EPOCH and directory == CONTEXT.directory and quarantine is False)
|
||||
require(head < json.loads((ROOT / 'checkpoint.json').read_text())['head'])
|
||||
refused(lambda: store().check())
|
||||
refused(lambda: store().reserve('no-replay-after-restore', '6' * 64))
|
||||
return {'actual_pg_restore_behind_checkpoint_detected': True, 'restored_store_cannot_gain_replay_authority': True}
|
||||
|
||||
|
||||
def lost_ack():
|
||||
class LoseAcknowledgment(FileCheckpoint):
|
||||
def advance(self, *args):
|
||||
super().advance(*args)
|
||||
raise RuntimeError('synthetic-lost-ack-after-durable-checkpoint')
|
||||
directory = Directory()
|
||||
directory.group_provenance = store(checkpoint=LoseAcknowledgment())
|
||||
refused(lambda: directory.ensure_service_groups(req(idempotency_key='synthetic-lost-sql-ack-key')))
|
||||
require(not directory.calls)
|
||||
directory.group_provenance = store()
|
||||
refused(lambda: directory.ensure_service_groups(req(idempotency_key='synthetic-lost-sql-ack-key')))
|
||||
require(not directory.calls)
|
||||
receipt = store().get('synthetic-lost-sql-ack-key', directory._group_binding(req()['groups'], req()['members'], req()['correlation_id']))
|
||||
require(receipt['state'] == 'pending')
|
||||
refused(lambda: store().reserve('unrelated-after-unresolved', '7' * 64))
|
||||
# Definite no-directory-call refusal is disposed only inside this generated
|
||||
# fixture, before moving to the next synthetic fault. No native recovery API.
|
||||
receipt['state'] = 'refused'
|
||||
store().save(receipt)
|
||||
directory = Directory()
|
||||
directory.group_provenance = store()
|
||||
original = directory._gql
|
||||
def lose_directory_response(token, query, variables):
|
||||
result = original(token, query, variables)
|
||||
if 'addUserToGroup' in query:
|
||||
raise RuntimeError('synthetic-directory-response-lost')
|
||||
return result
|
||||
directory._gql = lose_directory_response
|
||||
payload = req(idempotency_key='synthetic-lost-directory-response')
|
||||
refused(lambda: directory.ensure_service_groups(payload))
|
||||
checked = snapshot(directory)
|
||||
directory.group_provenance = store()
|
||||
refused(lambda: directory.ensure_service_groups(payload))
|
||||
require(snapshot(directory) == checked)
|
||||
record = store().get(payload['idempotency_key'], directory._group_binding(payload['groups'], payload['members'], payload['correlation_id']))
|
||||
require(record['state'] == 'uncertain' and record['intent']['operation'] == 'add_member')
|
||||
class LostCommitReply:
|
||||
def __init__(self): self.db = psycopg.connect(**info())
|
||||
def __enter__(self): self.db.__enter__(); return self
|
||||
def __exit__(self, typ, value, traceback):
|
||||
result = self.db.__exit__(typ, value, traceback)
|
||||
if typ is None: raise OSError('synthetic-reply-lost-after-actual-sql-commit')
|
||||
return result
|
||||
def cursor(self): return self.db.cursor()
|
||||
def close(self): self.db.close()
|
||||
broken = PostgresGroupProvenance(connect=LostCommitReply, binding=CONTEXT, continuity=FileCheckpoint())
|
||||
checkpoint = json.loads((ROOT / 'checkpoint.json').read_text())['head']
|
||||
refused(lambda: broken.save(record))
|
||||
with psycopg.connect(**info()) as db:
|
||||
committed_head = db.execute('SELECT head FROM identity_provisioner.group_journal_meta').fetchone()[0]
|
||||
require(committed_head == checkpoint + 1)
|
||||
require(json.loads((ROOT / 'checkpoint.json').read_text())['head'] == checkpoint)
|
||||
refused(lambda: store().check())
|
||||
return {'lost_checkpoint_ack_retains_pending_reservation': True, 'reopened_uncertainty_refuses_ldap_effects': True,
|
||||
'active_fence_survives_process_reopen': True, 'lost_synthetic_directory_response_retains_postgres_intent': True,
|
||||
'lost_sql_commit_reply_retains_committed_head': True, 'reopened_stale_checkpoint_refuses_continuation': True}
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
stage('loopback_check')
|
||||
require({p.name for p in Path('/sys/class/net').iterdir()} == {'lo'})
|
||||
require(len(Path('/proc/net/route').read_text().splitlines()) == 1)
|
||||
checks = {'only_loopback_no_external_routes': True}
|
||||
stage('phase_dispatch')
|
||||
checks.update({'bootstrap': bootstrap, 'restart': restart, 'concurrency': concurrency,
|
||||
'fork': fork, 'restored': restored, 'lost-ack': lost_ack}[sys.argv[1]]())
|
||||
stage('phase_complete')
|
||||
print(json.dumps({'status': 'passed', 'checks': checks, 'psycopg_version': psycopg.__version__, 'active_database': info()['dbname']}))
|
||||
except Exception as error:
|
||||
print(json.dumps({'status': 'failed', 'failure': 'synthetic_pg_oracle_failed',
|
||||
'diagnostic': error_metadata(error, CURRENT_STAGE)}), flush=True)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1
|
||||
|
||||
RUN useradd --create-home --uid 10001 user-engine
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml README.md /app/
|
||||
COPY src /app/src
|
||||
RUN pip install --no-cache-dir ".[runtime]"
|
||||
|
||||
USER 10001:10001
|
||||
EXPOSE 8080
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
CMD ["python", "-m", "user_engine.runtime"]
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# Prepared disposable PostgreSQL compatibility fixture
|
||||
|
||||
Status: prepared and unit checked; not executed or approved. Default
|
||||
`python3 driver.py --plan` reads only the manifest, without loading containment,
|
||||
generating material or calling Docker. Execution requires independent review of
|
||||
the exact manifest/source closure and explicit execution flag/digest. The flag
|
||||
and fixture files confer no authority.
|
||||
|
||||
The fixture reuses the existing `isolated-keycape.py` command/environment guard,
|
||||
containment inspector and exact name/label cleanup. A network-none PostgreSQL
|
||||
anchor and a pinned cached User Engine Python helper share only loopback. Both
|
||||
are nonroot/read-only/capability-free, with private bounded tmpfs, no host ports,
|
||||
mounts or Docker socket, and no pulls/builds/packages. Generated admin/runtime
|
||||
passwords enter through stdin only. Image-specific nonroot UID999/Python/psycopg
|
||||
compatibility is an explicit precondition checked by execution; no cache or
|
||||
runtime claim is made during preparation. Any incompatible image/anonymous
|
||||
volume/error fails closed and still invokes exact cleanup.
|
||||
|
||||
Six sequential Python phases use the actual candidate SQL backend and its
|
||||
normal provisioner path with the **existing synthetic LDAP Directory**. Bootstrap
|
||||
runs the real migration on generated state, checks runtime DDL denial and exact
|
||||
created-only inventories. A separate process reopens and replays/revokes while
|
||||
preserving the complete synthetic prestate. Concurrent reservations must yield
|
||||
one original receipt; stale row CAS cannot advance its head. Two actual database
|
||||
clones then pass the same old checkpoint check concurrently and commit different
|
||||
receipts at the same next counter. Only one external digest CAS may win; a fresh
|
||||
backend on the losing clone must refuse even at that same counter. Lost checkpoint
|
||||
acknowledgment, lost synthetic directory response and a lost reply after actual
|
||||
SQL commit must retain pending/uncertain state and refuse continuation. An actual
|
||||
pg_dump taken after bootstrap is then pg_restored after later journal changes;
|
||||
the external test checkpoint survives. Runtime SQL SELECT must work and show an
|
||||
older restored head before the candidate's restore-quarantine refusal oracle can
|
||||
pass. This prevents an unrelated connection/ACL failure from masquerading as
|
||||
restore protection.
|
||||
|
||||
All fresh names/labels are registered before create. `finally` uses the existing
|
||||
cleanup helper for both resources, requires successful exact absence inventories
|
||||
and retains any Docker uncertainty as failed. PID1 lifetimes and RPC/execution
|
||||
limits are bounded in the manifest. This is not a parent-death/reaper proof.
|
||||
Only safe booleans, public image/source/version pins and cleanup duties leave in
|
||||
a private `/tmp/nk48-postgres-native-review-*/result.json`; synthetic backup bytes
|
||||
are bounded in host memory and never written as an archive file.
|
||||
|
||||
The test checkpoint implementation belongs only to this fixture. Its independent
|
||||
file survives PostgreSQL restore and uses file locking/fsync; it is not offered
|
||||
as native custody, case authority, a journal-recovery API or an evidence broker.
|
||||
The fixture explicitly disposes one definitely pre-LDAP synthetic reservation to
|
||||
move between fault cases; it does not clear an unknown native mutation. All
|
||||
source/fixture passes remain distinct from live CNPG/lease/TLS/backup continuity,
|
||||
protected owner callbacks, actual authoritative tenant scope, native LDAP fencing
|
||||
and production acceptance.
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"source": "coordinator message; raw command output not supplied to worker",
|
||||
"reviewed_manifest_sha256": "4a816f74f857dfbebb5eaa0de3d792c91cd5e0f52caf458c2f7789583507ee31",
|
||||
"independent_source_tests": "80 passed in8.56s",
|
||||
"independent_fixture_plan_tests": "8 passed in0.03s",
|
||||
"preflight_status": "failed_before_execute",
|
||||
"postgres_image_cached": true,
|
||||
"missing_helper_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:1e6547c40c93c49f01438edcf76c20f561ccebc110220bae020723505aa56c70",
|
||||
"execute_called": false,
|
||||
"resource_creation_attempted": false,
|
||||
"pull_or_override_used": false,
|
||||
"cached_helper_inventory_reported": [
|
||||
"sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b",
|
||||
"sha256:312dcb062b237c518148e1b4f09798a4e6ad08bc60d4add6834aab3f246f2002"
|
||||
],
|
||||
"scope": "read-only preflight failure is retained, not a successful native compatibility outcome",
|
||||
"structured_original_receipt": "../../../preflight-r1-failure.json",
|
||||
"structured_original_sha256": "88b2f48c18429fff745f881066eac1f7729233fc59fa676dc1eee7eccbe6ace4",
|
||||
"raw_stderr_file": "absent; original tool traceback was returned to coordinator and not captured as a file"
|
||||
}
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Default is a read-only plan. Docker execution awaits independent exact review."""
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import secrets
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
MANIFEST = HERE / 'manifest.json'
|
||||
|
||||
|
||||
def require(condition):
|
||||
if not condition:
|
||||
raise RuntimeError('bounded_synthetic_fixture_refused')
|
||||
|
||||
|
||||
def reviewed_manifest(expected):
|
||||
raw = MANIFEST.read_bytes()
|
||||
require(expected == hashlib.sha256(raw).hexdigest())
|
||||
manifest = json.loads(raw)
|
||||
for path, digest in manifest['source_sha256'].items():
|
||||
require(bool(re.fullmatch(r'sha256:[0-9a-f]{64}', digest)))
|
||||
require('sha256:' + hashlib.sha256(Path(path).read_bytes()).hexdigest() == digest)
|
||||
return manifest
|
||||
|
||||
|
||||
def load_containment(manifest):
|
||||
spec = importlib.util.spec_from_file_location('reviewed_existing_containment', manifest['containment_helper'])
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def session_plan(manifest, run_id):
|
||||
require(bool(re.fullmatch(r'[a-f0-9]{32}', run_id)))
|
||||
anchor = 'nk49-kc-' + run_id + '-journal-postgres'
|
||||
probe = 'nk49-kc-' + run_id + '-journal-probe'
|
||||
return {'run_id': run_id, 'containers': [anchor, probe], 'worker': anchor, 'issuer': probe,
|
||||
'images': {anchor: manifest['postgres_image'], probe: manifest['python_image']}, 'volumes': {}}
|
||||
|
||||
|
||||
def base_args(session, name, network, user="1000:1000"):
|
||||
uid, gid = user.split(":")
|
||||
return ['docker', 'run', '--detach', '--pull=never', '--name', name, '--network', network,
|
||||
'--label', 'net-kingdom.recovery-run=' + session['run_id'], '--read-only', '--cap-drop', 'ALL',
|
||||
'--security-opt', 'no-new-privileges', '--user', user, '--memory', '384m', '--cpus', '0.5',
|
||||
'--pids-limit', '64', '--log-driver', 'none', '--restart', 'no',
|
||||
'--tmpfs', '/tmp:rw,nosuid,nodev,noexec,size=16777216,uid=' + uid + ',gid=' + gid + ',mode=0700']
|
||||
|
||||
|
||||
def tar_bytes(files):
|
||||
out = io.BytesIO()
|
||||
with tarfile.open(fileobj=out, mode='w') as archive:
|
||||
for name, value in files.items():
|
||||
require(bool(re.fullmatch(r'[a-zA-Z0-9._/-]+', name)) and not name.startswith('/') and '..' not in Path(name).parts)
|
||||
entry = tarfile.TarInfo(name)
|
||||
entry.size = len(value)
|
||||
entry.mode = 0o600
|
||||
entry.uid = entry.gid = 1000
|
||||
archive.addfile(entry, io.BytesIO(value))
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
IMPORT = r'''
|
||||
import base64,json,os,sys
|
||||
from pathlib import Path
|
||||
os.umask(0o077)
|
||||
root=Path('/fixture')
|
||||
(root/'source').mkdir(mode=0o700)
|
||||
(root/'source/migrations').mkdir(mode=0o700)
|
||||
(root/'source/tests').mkdir(mode=0o700)
|
||||
for name,encoded in json.load(sys.stdin).items():
|
||||
if name not in ('source/provisioner.py','source/group_provenance.py','source/group_provenance_postgres.py','source/migrations/group_journal_postgres_v1.sql','source/tests/test_service_groups.py','probe.py','private.json'):raise ValueError()
|
||||
fd=os.open(root/name,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
with os.fdopen(fd,'wb') as output:output.write(base64.b64decode(encoded,validate=True))
|
||||
'''
|
||||
|
||||
|
||||
def preflight(adapter, manifest):
|
||||
adapter.docker_environment() # Refuse ambient alternate routing before calls.
|
||||
contexts = json.loads(adapter.command(['docker', 'context', 'inspect'], timeout=15))
|
||||
require(len(contexts) == 1 and contexts[0]['Endpoints']['docker']['Host'] == manifest['fixed_endpoint'])
|
||||
facts = json.loads(adapter.command(['docker', 'info', '--format',
|
||||
'{"ID":{{json .ID}},"Name":{{json .Name}},"OSType":{{json .OSType}}}'], timeout=15))
|
||||
require(facts == {'ID': manifest['engine_id'], 'Name': manifest['engine_name'], 'OSType': 'linux'})
|
||||
for image in (manifest['postgres_image'], manifest['python_image']):
|
||||
value = adapter.command(['docker', 'image', 'inspect', '--format', '{{.Id}}', image], timeout=15).decode().strip()
|
||||
require(bool(re.fullmatch(r'sha256:[a-f0-9]{64}', value)))
|
||||
|
||||
|
||||
def execute(manifest, adapter, result):
|
||||
session = session_plan(manifest, uuid.uuid4().hex)
|
||||
result['session'] = {'run_id': session['run_id'], 'containers': session['containers'],
|
||||
'label': 'net-kingdom.recovery-run=' + session['run_id']}
|
||||
deadline = time.monotonic() + 210
|
||||
original_command = adapter.command
|
||||
def run(argv, **kwargs):
|
||||
remaining = deadline - time.monotonic()
|
||||
require(remaining > 0)
|
||||
try:
|
||||
return original_command(argv, timeout=min(50, remaining), **kwargs)
|
||||
except BaseException:
|
||||
result['daemon_request_uncertain'] = True
|
||||
raise
|
||||
adapter.command = run
|
||||
try:
|
||||
anchor = session['worker']
|
||||
args = base_args(session, anchor, 'none', user='999:999') + [
|
||||
'--tmpfs', '/var/lib/postgresql:rw,nosuid,nodev,noexec,size=1048576,uid=999,gid=999,mode=0700',
|
||||
'--tmpfs', '/var/lib/postgresql/data:rw,nosuid,nodev,noexec,size=134217728,uid=999,gid=999,mode=0700',
|
||||
'--tmpfs', '/run/fixture:rw,nosuid,nodev,noexec,size=1048576,uid=999,gid=999,mode=0700',
|
||||
'--env', 'PGDATA=/var/lib/postgresql/data', '--entrypoint', '/bin/sh',
|
||||
manifest['postgres_image'], '-c', 'sleep 240']
|
||||
run(args)
|
||||
adapter.inspect_session({**session, 'containers': [anchor], 'issuer': anchor}, worker_only=True)
|
||||
admin_password, runtime_password = secrets.token_hex(24), secrets.token_hex(24)
|
||||
run(['docker', 'exec', '-i', anchor, 'tar', '-xf', '-', '-C', '/run/fixture'],
|
||||
input=tar_bytes({'password': admin_password.encode()}))
|
||||
run(['docker', 'exec', '--detach', anchor, '/bin/sh', '-c',
|
||||
'initdb -D /var/lib/postgresql/data --username=fixture_admin --auth-local=trust --auth-host=scram-sha-256 --pwfile=/run/fixture/password > /dev/null 2>&1 && exec postgres -D /var/lib/postgresql/data -c listen_addresses=127.0.0.1 -c unix_socket_directories=/run/fixture > /dev/null 2>&1'])
|
||||
anchor_id = run(['docker', 'container', 'inspect', anchor, '--format', '{{.Id}}']).decode().strip()
|
||||
require(bool(re.fullmatch(r'[a-f0-9]{64}', anchor_id)))
|
||||
args = base_args(session, session['issuer'], 'container:' + anchor_id) + [
|
||||
'--tmpfs', '/fixture:rw,nosuid,nodev,noexec,size=16777216,uid=1000,gid=1000,mode=0700',
|
||||
'--entrypoint', 'python3', manifest['python_image'], '-c', 'import time;time.sleep(240)']
|
||||
run(args)
|
||||
adapter.inspect_session(session, worker_only=True)
|
||||
result['containment_verified_before_generated_private_import'] = True
|
||||
files = {'source/' + name: (HERE.parent / 'postimage/identity-provisioner' / name).read_bytes()
|
||||
for name in ('provisioner.py', 'group_provenance.py', 'group_provenance_postgres.py',
|
||||
'migrations/group_journal_postgres_v1.sql')}
|
||||
files['source/tests/test_service_groups.py'] = (HERE / 'test_service_groups.py').read_bytes()
|
||||
files.update({'probe.py': (HERE / 'probe.py').read_bytes(), 'private.json': json.dumps({
|
||||
'admin_password': admin_password, 'runtime_password': runtime_password}).encode()})
|
||||
run(['docker', 'exec', '-i', session['issuer'], 'python3', '-c', IMPORT],
|
||||
input=json.dumps({k: base64.b64encode(v).decode() for k, v in files.items()}).encode())
|
||||
result['checks'] = {}
|
||||
backup = None
|
||||
active_database = 'postgres'
|
||||
for phase in ('bootstrap', 'restart', 'concurrency', 'fork', 'lost-ack', 'restored'):
|
||||
if phase == 'restored':
|
||||
require(backup is not None)
|
||||
run(['docker', 'exec', '-i', anchor, 'pg_restore', '--host', '/run/fixture', '-U', 'fixture_admin',
|
||||
'--dbname', active_database, '--clean', '--if-exists', '--single-transaction', '--exit-on-error', '--no-owner'], input=backup)
|
||||
value = json.loads(run(['docker', 'exec', session['issuer'], 'python3', '/fixture/probe.py', phase], max_output=8192))
|
||||
expected = set(manifest['phase_oracles'][phase])
|
||||
require(value.get('status') == 'passed' and set(value.get('checks', {})) == expected)
|
||||
require(all(v is True for v in value['checks'].values()))
|
||||
result['checks'].update(value['checks'])
|
||||
result['psycopg_version'] = value['psycopg_version']
|
||||
require(value.get('active_database') in {'postgres', 'fixture_fork'})
|
||||
active_database = value['active_database']
|
||||
if phase == 'bootstrap':
|
||||
backup = run(['docker', 'exec', anchor, 'pg_dump', '--host', '/run/fixture', '-U', 'fixture_admin',
|
||||
'--dbname', 'postgres', '--format=custom', '--no-owner'], max_output=4194304)
|
||||
result['status'] = 'synthetic_postgres_journal_compatibility_passed'
|
||||
finally:
|
||||
def cleanup_command(argv, **kwargs):
|
||||
try: return original_command(argv, timeout=15, **kwargs)
|
||||
except BaseException:
|
||||
result['daemon_request_uncertain'] = True
|
||||
raise
|
||||
adapter.command = cleanup_command
|
||||
try:
|
||||
adapter.cleanup(session, result)
|
||||
result['container_cleanup_verified'] = True
|
||||
except BaseException:
|
||||
result['status'] = 'failed'
|
||||
result['container_cleanup_verified'] = False
|
||||
adapter.command = original_command
|
||||
if result.get('daemon_request_uncertain'):
|
||||
result['status'] = 'failed'
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--plan', action='store_true')
|
||||
parser.add_argument('--execute-reviewed-fixture', action='store_true')
|
||||
parser.add_argument('--expected-manifest-sha256')
|
||||
args = parser.parse_args()
|
||||
if not args.execute_reviewed_fixture:
|
||||
print(MANIFEST.read_text())
|
||||
return 0
|
||||
require(not args.plan)
|
||||
manifest = reviewed_manifest(args.expected_manifest_sha256)
|
||||
adapter = load_containment(manifest)
|
||||
preflight(adapter, manifest)
|
||||
os.umask(0o077)
|
||||
output = Path(tempfile.mkdtemp(prefix='nk48-postgres-native-review-', dir='/tmp')) / 'result.json'
|
||||
result = {'schema': 'nk48.synthetic-postgres-journal-result.v1', 'status': 'failed',
|
||||
'manifest_sha256': args.expected_manifest_sha256, 'production_contacted': False,
|
||||
'actual_credentials_used': False, 'native_acceptance_claimed': False}
|
||||
try:
|
||||
execute(manifest, adapter, result)
|
||||
except BaseException:
|
||||
result['status'] = 'failed'
|
||||
finally:
|
||||
output.write_text(json.dumps(result, indent=2) + '\n')
|
||||
print(json.dumps({'status': result['status'], 'safe_result': str(output)}))
|
||||
return int(result['status'] != 'synthetic_postgres_journal_compatibility_passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
"schema": "nk48.postgres-journal-fixture-proposal.v1",
|
||||
"status": "prepared_not_executed_not_approved",
|
||||
"source_sha256": {
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py": "sha256:54cc76913e7fdaa5c75cc31040aea33378792bf10393210339a5dbd5973db32c",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/probe.py": "sha256:97265a65f008e86c6f3de54eaa1a7d77f069117d4b923fb85cf835a8816adf00",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_driver.py": "sha256:28a96975546c82d8e42f302d14f29051b18ed6264839234698802ac1ef09f500",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_service_groups.py": "sha256:815571fd92c0001dacbe250640acd99b2f2872b4171519ed54f8f469ee90aafe",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/source-manifest.json": "sha256:0c16e19415ad93b6ca73c92a05c205d263adde8a780c8c33119cd5f9d40f4752",
|
||||
"/home/worsch/net-kingdom/tools/bootstrap-recovery/isolated-keycape.py": "sha256:f67627b7d1647d75187dd899874efe22383bc91de5d97dabb9801076668ab3c2",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/provisioner.py": "sha256:19d1b837cbafeeacb1aa815856cd68091e6702281d72b1d860230fa320e1725b",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/group_provenance.py": "sha256:405e08eaaab7c21d618dff4db5970607806f32ab6c461effb81c737e32182b10",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/group_provenance_postgres.py": "sha256:55282af26f95af66ff8d444d38d0bcbcf3ee58a615adedfff8da5db2b0716889",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/migrations/group_journal_postgres_v1.sql": "sha256:3cdf82194d6f1e2dd865b0c7e7b8a14447fda8c49151148134a0d40a80fb46ac",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/README.md": "sha256:c762987d320d745289d1a9068a2994b0c9c6d9bb53a545ad03841e434c51c040",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/README.md": "sha256:4bf4678a1f1c70fbd3d4ffa77c70821be0372f03210b2164a2895fc99ca6a6b9"
|
||||
},
|
||||
"candidate_patch_sha256": "503e3d0217e6be25df8a58f3968cab7c00cdd9933d0fee2a66ef951fc4aae2b1",
|
||||
"containment_helper": "/home/worsch/net-kingdom/tools/bootstrap-recovery/isolated-keycape.py",
|
||||
"fixed_endpoint": "unix:///var/run/docker.sock",
|
||||
"engine_id": "74f93783-6cef-43a8-a3c4-f62586cd3bc1",
|
||||
"engine_name": "bnt-lap001",
|
||||
"postgres_image": "postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70",
|
||||
"python_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:1e6547c40c93c49f01438edcf76c20f561ccebc110220bae020723505aa56c70",
|
||||
"image_pin_origin": "existing check-privacyidea-database.py and rapp-user-engine/manifests/runtime.yaml; cache presence not queried; missing image refuses without pull",
|
||||
"pull_policy": "never",
|
||||
"network": "network-none",
|
||||
"scope": "real disposable PostgreSQL backend SQL/migration/concurrency/restore compatibility; LDAP is explicitly the existing synthetic test Directory, not actual native LDAP in this fixture",
|
||||
"limits": {
|
||||
"max_service_containers": 2,
|
||||
"host_ports": 0,
|
||||
"host_mounts": 0,
|
||||
"named_or_anonymous_volumes": 0,
|
||||
"memory_bytes_per_container": 402653184,
|
||||
"cpus_per_container": 0.5,
|
||||
"pids_per_container": 64,
|
||||
"postgres_tmpfs_bytes": 153092096,
|
||||
"helper_tmpfs_bytes": 33554432,
|
||||
"backup_in_host_memory_max_bytes": 4194304,
|
||||
"container_lifetime_seconds": 240,
|
||||
"execution_deadline_seconds": 210,
|
||||
"docker_rpc_timeout_seconds": 50,
|
||||
"preflight_rpc_timeout_seconds": 15,
|
||||
"cleanup_rpc_timeout_seconds": 15,
|
||||
"cleanup_total_bound_seconds": 120,
|
||||
"new_cloud_spend_eur": 0
|
||||
},
|
||||
"compatibility_preconditions": [
|
||||
"exact cached image/engine pins reverified by admitted preflight, never pulled",
|
||||
"PostgreSQL image supports nonroot UID999 with initdb/postgres/pg_dump/pg_restore in PATH; failed compatibility refuses/cleans",
|
||||
"User Engine pinned image includes psycopg3; exact bundled version reported; no dependency downloads/install/build in fixture",
|
||||
"all image-declared volumes must be masked by private tmpfs; existing containment guard refuses any anonymous volume before private import"
|
||||
],
|
||||
"resources": {
|
||||
"container_names": "nk49-kc-<fresh32hex>-journal-{postgres,probe}",
|
||||
"exact_label": "net-kingdom.recovery-run=<same-fresh32hex>",
|
||||
"private_state": "generated admin/runtime passwords, source/migration, synthetic directory, external test checkpoint within private container tmpfs",
|
||||
"safe_result": "private /tmp/nk48-postgres-native-review-<suffix>/result.json"
|
||||
},
|
||||
"phase_oracles": {
|
||||
"bootstrap": [
|
||||
"only_loopback_no_external_routes",
|
||||
"actual_postgresql_journal_ensure",
|
||||
"explicit_scope_context_retained",
|
||||
"confirmed_inventory_excludes_preexisting",
|
||||
"runtime_ddl_denied"
|
||||
],
|
||||
"restart": [
|
||||
"only_loopback_no_external_routes",
|
||||
"separate_process_postgres_restart_and_revoke",
|
||||
"preexisting_shared_state_preserved",
|
||||
"foreign_context_receipt_refused"
|
||||
],
|
||||
"concurrency": [
|
||||
"only_loopback_no_external_routes",
|
||||
"concurrent_original_reservation_exactly_once",
|
||||
"stale_version_update_refused_without_head_advance"
|
||||
],
|
||||
"lost-ack": [
|
||||
"only_loopback_no_external_routes",
|
||||
"lost_checkpoint_ack_retains_pending_reservation",
|
||||
"reopened_uncertainty_refuses_ldap_effects",
|
||||
"active_fence_survives_process_reopen",
|
||||
"lost_synthetic_directory_response_retains_postgres_intent",
|
||||
"lost_sql_commit_reply_retains_committed_head",
|
||||
"reopened_stale_checkpoint_refuses_continuation"
|
||||
],
|
||||
"restored": [
|
||||
"only_loopback_no_external_routes",
|
||||
"actual_pg_restore_behind_checkpoint_detected",
|
||||
"restored_store_cannot_gain_replay_authority"
|
||||
],
|
||||
"fork": [
|
||||
"only_loopback_no_external_routes",
|
||||
"concurrent_divergent_clones_same_counter_verified",
|
||||
"independent_exact_transition_cas_selects_one_winner",
|
||||
"reopened_loser_clone_refused_at_same_counter"
|
||||
]
|
||||
},
|
||||
"restore": "actual generated pg_dump after bootstrap; winner database advances through reopen/concurrency/fork/uncertainty; actual pg_restore of original archive while independent content checkpoint survives; runtime SELECT must work and show older restored head before refusal can pass",
|
||||
"cleanup": "finally reuses exact name/label cleanup for both preregistered containers, including failed/lost-create replies; successful explicit absence required; any Docker uncertainty remains failed; no prune/global removal",
|
||||
"not_claimed": [
|
||||
"CNPG HA, native runtime storage/backup/restore admission",
|
||||
"authoritative tenant/case/PDP/evidence/session owner callbacks",
|
||||
"external LDAP writer fencing/CAS/ABA safety",
|
||||
"production endpoint activation, credentials or owner assent",
|
||||
"KeyCape hard-parent-loss reaper proof"
|
||||
],
|
||||
"forbidden": [
|
||||
"Docker commands before exact independent review",
|
||||
"pull/build/push, host networking/ports/mounts, production APIs",
|
||||
"real database/LDAP, identity archives, credentials from environment/custody",
|
||||
"clearing production restore quarantine or manufacturing scope/authority"
|
||||
],
|
||||
"execution_command": [
|
||||
"python3",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py",
|
||||
"--execute-reviewed-fixture",
|
||||
"--expected-manifest-sha256",
|
||||
"<reviewed-exact-manifest-digest>"
|
||||
],
|
||||
"fork": "actual PostgreSQL CREATE DATABASE TEMPLATE clone; both stores pass exact same old external marker check under test barrier, commit different bodies at same counter, race external strict CAS; exactly one wins and fresh loser store refuses; no directory effect in this direct reservation case"
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"status": "preflight_refused",
|
||||
"reviewed_manifest_sha256": "sha256:4a816f74f857dfbebb5eaa0de3d792c91cd5e0f52caf458c2f7789583507ee31",
|
||||
"phase": "preflight.image_inspect",
|
||||
"postgres_exact_pin_cached": true,
|
||||
"user_engine_exact_pin_cached": false,
|
||||
"missing_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:1e6547c40c93c49f01438edcf76c20f561ccebc110220bae020723505aa56c70",
|
||||
"docker_session_created": false,
|
||||
"generated_private_import_started": false,
|
||||
"pull_or_build_attempted": false,
|
||||
"native_acceptance": false,
|
||||
"evidence_basis": "Coordinator tool-returned traceback at preflight image inspect, followed by exact metadata-only image inspection; no original stderr file was captured."
|
||||
}
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
"""Synthetic PostgreSQL journal compatibility; LDAP remains a declared test double."""
|
||||
import concurrent.futures
|
||||
from dataclasses import replace
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import uuid
|
||||
|
||||
ROOT = Path('/fixture')
|
||||
sys.path[:0] = [str(ROOT / 'source'), str(ROOT / 'source/tests')]
|
||||
import psycopg
|
||||
from group_provenance import GroupProvenanceUnavailable
|
||||
from group_provenance_postgres import GroupJournalBinding, PostgresGroupProvenance, psycopg_connector, journal_genesis
|
||||
from test_service_groups import Directory, req
|
||||
|
||||
EPOCH = 'bfbb8939-e88c-4f9c-a5c6-14ba3e399ddf'
|
||||
CONTEXT = GroupJournalBinding('tenant:synthetic:one', 'synthetic-pg-directory', 'sha256:' + '1' * 64,
|
||||
'sha256:' + '2' * 64, 'synthetic-principal', EPOCH)
|
||||
|
||||
|
||||
def require(value):
|
||||
if not value:
|
||||
raise RuntimeError('postgres_fixture_oracle_failed')
|
||||
|
||||
|
||||
def info(admin=False, database=None):
|
||||
values = json.loads((ROOT / 'private.json').read_text())
|
||||
database = database or ((ROOT / 'active-database').read_text() if (ROOT / 'active-database').exists() else 'postgres')
|
||||
require(database in {'postgres', 'fixture_fork'})
|
||||
return {'host': '127.0.0.1', 'port': 5432, 'dbname': database, 'user': 'fixture_admin' if admin else 'fixture_app',
|
||||
'password': values['admin_password'] if admin else values['runtime_password'], 'sslmode': 'disable'}
|
||||
|
||||
|
||||
class FileCheckpoint:
|
||||
"""Fixture-only independently retained checkpoint; never a native owner grant."""
|
||||
def __init__(self):
|
||||
self.path = ROOT / 'checkpoint.json'
|
||||
def _apply(self, epoch, directory, previous, previous_digest, current=None, current_digest=None):
|
||||
with (ROOT / 'checkpoint.lock').open('a+') as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
value = json.loads(self.path.read_text())
|
||||
require(value == {'epoch': epoch, 'directory': directory, 'head': previous, 'digest': previous_digest})
|
||||
if current is not None:
|
||||
temporary = ROOT / ('checkpoint-' + uuid.uuid4().hex)
|
||||
with temporary.open('x') as out:
|
||||
json.dump({'epoch': epoch, 'directory': directory, 'head': current, 'digest': current_digest}, out)
|
||||
out.flush(); os.fsync(out.fileno())
|
||||
os.replace(temporary, self.path)
|
||||
fd = os.open(ROOT, os.O_RDONLY | os.O_DIRECTORY)
|
||||
try: os.fsync(fd)
|
||||
finally: os.close(fd)
|
||||
return True
|
||||
def check(self, epoch, directory, head, digest):
|
||||
return self._apply(epoch, directory, head, digest)
|
||||
def advance(self, epoch, directory, previous, previous_digest, current, current_digest):
|
||||
return self._apply(epoch, directory, previous, previous_digest, current, current_digest)
|
||||
|
||||
|
||||
def store(context=CONTEXT, checkpoint=None, database=None):
|
||||
return PostgresGroupProvenance(connect=psycopg_connector(connection_info=lambda: info(database=database)), binding=context,
|
||||
continuity=checkpoint or FileCheckpoint())
|
||||
|
||||
|
||||
def refused(call):
|
||||
try: call()
|
||||
except (ValueError, RuntimeError): return
|
||||
raise RuntimeError('postgres_fixture_expected_refusal_missing')
|
||||
|
||||
|
||||
def snapshot(directory):
|
||||
return {'users': sorted(directory.users), 'groups': directory.groups,
|
||||
'members': {str(k): sorted(v) for k, v in directory.members.items()}}
|
||||
|
||||
|
||||
def restore_directory(value):
|
||||
directory = Directory()
|
||||
directory.users = set(value['users'])
|
||||
directory.groups = value['groups']
|
||||
directory.members = {int(k): set(v) for k, v in value['members'].items()}
|
||||
directory.group_provenance = store()
|
||||
return directory
|
||||
|
||||
|
||||
def bootstrap():
|
||||
for _ in range(80):
|
||||
try:
|
||||
db = psycopg.connect(**info(admin=True), connect_timeout=1)
|
||||
db.close(); break
|
||||
except Exception: time.sleep(.25)
|
||||
else: raise RuntimeError('synthetic_postgres_not_ready')
|
||||
from psycopg import sql
|
||||
with psycopg.connect(**info(admin=True)) as db:
|
||||
db.execute('CREATE SCHEMA identity_provisioner')
|
||||
db.execute((ROOT / 'source/migrations/group_journal_postgres_v1.sql').read_text())
|
||||
db.execute('INSERT INTO identity_provisioner.group_journal_meta VALUES (true, %s, %s, 0, %s, false)', (EPOCH, CONTEXT.directory, journal_genesis(EPOCH, CONTEXT.directory)))
|
||||
db.execute(sql.SQL('CREATE ROLE fixture_app LOGIN PASSWORD {}').format(sql.Literal(info()['password'])))
|
||||
db.execute('GRANT USAGE ON SCHEMA identity_provisioner TO fixture_app')
|
||||
db.execute('GRANT SELECT, INSERT, UPDATE ON identity_provisioner.group_receipts TO fixture_app')
|
||||
db.execute('GRANT SELECT ON identity_provisioner.group_journal_meta TO fixture_app')
|
||||
db.execute('GRANT UPDATE (head,head_digest) ON identity_provisioner.group_journal_meta TO fixture_app')
|
||||
(ROOT / 'checkpoint.json').write_text(json.dumps({'epoch': EPOCH, 'directory': CONTEXT.directory, 'head': 0, 'digest': journal_genesis(EPOCH, CONTEXT.directory)}))
|
||||
directory = Directory()
|
||||
directory.members[3].add('tegwick')
|
||||
before = snapshot(directory)
|
||||
directory.group_provenance = store()
|
||||
result = directory.ensure_service_groups(req())
|
||||
require(result.status == 'groups_ensured')
|
||||
require({x['name'] for x in result.created_groups} == {'forge-coulomb-owners', 'forge-coulomb-readers'})
|
||||
require({(x['name'], x['username']) for x in result.created_memberships} == {('forge-coulomb-owners', 'tegwick')})
|
||||
(ROOT / 'directory.json').write_text(json.dumps({'before': before, 'after': snapshot(directory)}))
|
||||
with psycopg.connect(**info()) as db:
|
||||
try: db.execute('CREATE TABLE identity_provisioner.runtime_ddl_must_fail (id integer)')
|
||||
except psycopg.errors.InsufficientPrivilege: pass
|
||||
else: raise RuntimeError('runtime_ddl_not_denied')
|
||||
return {'actual_postgresql_journal_ensure': True, 'explicit_scope_context_retained': True,
|
||||
'confirmed_inventory_excludes_preexisting': True, 'runtime_ddl_denied': True}
|
||||
|
||||
|
||||
def restart():
|
||||
saved = json.loads((ROOT / 'directory.json').read_text())
|
||||
directory = restore_directory(saved['after'])
|
||||
require(directory.ensure_service_groups(req()).resumed)
|
||||
require(not directory.calls)
|
||||
result = directory.revoke_service_groups(req(ensure_idempotency_key=req()['idempotency_key'],
|
||||
ensure_correlation_id=req()['correlation_id'], delete_if_empty=True))
|
||||
require(result.status == 'groups_revoked_and_deleted')
|
||||
require(snapshot(directory) == saved['before'])
|
||||
foreign = store(replace(CONTEXT, principal='other-synthetic-principal'))
|
||||
refused(lambda: foreign.get(req()['idempotency_key'], directory._group_binding(req()['groups'], req()['members'], req()['correlation_id'])))
|
||||
return {'separate_process_postgres_restart_and_revoke': True, 'preexisting_shared_state_preserved': True,
|
||||
'foreign_context_receipt_refused': True}
|
||||
|
||||
|
||||
def concurrency():
|
||||
binding = '4' * 64
|
||||
def reserve(_):
|
||||
try: return store().reserve('synthetic-concurrent-reservation', binding)
|
||||
except GroupProvenanceUnavailable: return None
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool:
|
||||
results = list(pool.map(reserve, range(24)))
|
||||
created = [r[0] for r in results if r is not None and r[1]]
|
||||
require(len(created) == 1)
|
||||
receipt = created[0]
|
||||
receipt['state'] = 'refused'
|
||||
store().save(receipt)
|
||||
stale = json.loads(json.dumps(receipt))
|
||||
store().save(receipt)
|
||||
head = json.loads((ROOT / 'checkpoint.json').read_text())['head']
|
||||
refused(lambda: store().save(stale))
|
||||
require(json.loads((ROOT / 'checkpoint.json').read_text())['head'] == head)
|
||||
with psycopg.connect(**info()) as db:
|
||||
require(db.execute('SELECT count(*) FROM identity_provisioner.group_receipts WHERE key=%s', ('synthetic-concurrent-reservation',)).fetchone()[0] == 1)
|
||||
return {'concurrent_original_reservation_exactly_once': True, 'stale_version_update_refused_without_head_advance': True}
|
||||
|
||||
|
||||
def fork():
|
||||
# Clone actual PostgreSQL state at the exact same head/digest. Both callers
|
||||
# must pass the old external check before either commits a divergent body.
|
||||
with psycopg.connect(**{**info(admin=True), 'dbname': 'template1'}, autocommit=True) as db:
|
||||
db.execute('CREATE DATABASE fixture_fork TEMPLATE postgres')
|
||||
barrier = threading.Barrier(2)
|
||||
class SimultaneousCheckpoint(FileCheckpoint):
|
||||
def check(self, *args):
|
||||
result = super().check(*args)
|
||||
barrier.wait(timeout=10)
|
||||
return result
|
||||
def attempt(database):
|
||||
candidate = store(database=database, checkpoint=SimultaneousCheckpoint())
|
||||
try:
|
||||
record, fresh = candidate.reserve('synthetic-fork-' + database, '8' * 64)
|
||||
require(fresh)
|
||||
return database, record
|
||||
except GroupProvenanceUnavailable:
|
||||
return database, None
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool:
|
||||
results = list(pool.map(attempt, ('postgres', 'fixture_fork')))
|
||||
winners = [(database, record) for database, record in results if record is not None]
|
||||
require(len(winners) == 1)
|
||||
winner, record = winners[0]
|
||||
loser = next(database for database, result in results if result is None)
|
||||
markers = {}
|
||||
for database in ('postgres', 'fixture_fork'):
|
||||
with psycopg.connect(**info(admin=True, database=database)) as db:
|
||||
markers[database] = db.execute('SELECT head,head_digest FROM identity_provisioner.group_journal_meta').fetchone()
|
||||
require(markers[winner][0] == markers[loser][0] and markers[winner][1] != markers[loser][1])
|
||||
store(database=winner).check()
|
||||
refused(lambda: store(database=loser).check())
|
||||
retained = json.loads((ROOT / 'checkpoint.json').read_text())
|
||||
require((retained['head'], retained['digest']) == markers[winner])
|
||||
# No directory call was attempted by this direct reservation fixture. Retain
|
||||
# terminal refusal for that key before continuing against the winner only.
|
||||
record['state'] = 'refused'
|
||||
store(database=winner).save(record)
|
||||
(ROOT / 'active-database').write_text(winner)
|
||||
return {'concurrent_divergent_clones_same_counter_verified': True,
|
||||
'independent_exact_transition_cas_selects_one_winner': True,
|
||||
'reopened_loser_clone_refused_at_same_counter': True}
|
||||
|
||||
|
||||
def restored():
|
||||
# Actual pg_restore has rolled SQL back while this checkpoint survived.
|
||||
with psycopg.connect(**info()) as db:
|
||||
epoch, directory, head, quarantine = db.execute('SELECT epoch::text,directory,head,quarantined FROM identity_provisioner.group_journal_meta').fetchone()
|
||||
require(epoch == EPOCH and directory == CONTEXT.directory and quarantine is False)
|
||||
require(head < json.loads((ROOT / 'checkpoint.json').read_text())['head'])
|
||||
refused(lambda: store().check())
|
||||
refused(lambda: store().reserve('no-replay-after-restore', '6' * 64))
|
||||
return {'actual_pg_restore_behind_checkpoint_detected': True, 'restored_store_cannot_gain_replay_authority': True}
|
||||
|
||||
|
||||
def lost_ack():
|
||||
class LoseAcknowledgment(FileCheckpoint):
|
||||
def advance(self, *args):
|
||||
super().advance(*args)
|
||||
raise RuntimeError('synthetic-lost-ack-after-durable-checkpoint')
|
||||
directory = Directory()
|
||||
directory.group_provenance = store(checkpoint=LoseAcknowledgment())
|
||||
refused(lambda: directory.ensure_service_groups(req(idempotency_key='synthetic-lost-sql-ack-key')))
|
||||
require(not directory.calls)
|
||||
directory.group_provenance = store()
|
||||
refused(lambda: directory.ensure_service_groups(req(idempotency_key='synthetic-lost-sql-ack-key')))
|
||||
require(not directory.calls)
|
||||
receipt = store().get('synthetic-lost-sql-ack-key', directory._group_binding(req()['groups'], req()['members'], req()['correlation_id']))
|
||||
require(receipt['state'] == 'pending')
|
||||
refused(lambda: store().reserve('unrelated-after-unresolved', '7' * 64))
|
||||
# Definite no-directory-call refusal is disposed only inside this generated
|
||||
# fixture, before moving to the next synthetic fault. No native recovery API.
|
||||
receipt['state'] = 'refused'
|
||||
store().save(receipt)
|
||||
directory = Directory()
|
||||
directory.group_provenance = store()
|
||||
original = directory._gql
|
||||
def lose_directory_response(token, query, variables):
|
||||
result = original(token, query, variables)
|
||||
if 'addUserToGroup' in query:
|
||||
raise RuntimeError('synthetic-directory-response-lost')
|
||||
return result
|
||||
directory._gql = lose_directory_response
|
||||
payload = req(idempotency_key='synthetic-lost-directory-response')
|
||||
refused(lambda: directory.ensure_service_groups(payload))
|
||||
checked = snapshot(directory)
|
||||
directory.group_provenance = store()
|
||||
refused(lambda: directory.ensure_service_groups(payload))
|
||||
require(snapshot(directory) == checked)
|
||||
record = store().get(payload['idempotency_key'], directory._group_binding(payload['groups'], payload['members'], payload['correlation_id']))
|
||||
require(record['state'] == 'uncertain' and record['intent']['operation'] == 'add_member')
|
||||
class LostCommitReply:
|
||||
def __init__(self): self.db = psycopg.connect(**info())
|
||||
def __enter__(self): self.db.__enter__(); return self
|
||||
def __exit__(self, typ, value, traceback):
|
||||
result = self.db.__exit__(typ, value, traceback)
|
||||
if typ is None: raise OSError('synthetic-reply-lost-after-actual-sql-commit')
|
||||
return result
|
||||
def cursor(self): return self.db.cursor()
|
||||
def close(self): self.db.close()
|
||||
broken = PostgresGroupProvenance(connect=LostCommitReply, binding=CONTEXT, continuity=FileCheckpoint())
|
||||
checkpoint = json.loads((ROOT / 'checkpoint.json').read_text())['head']
|
||||
refused(lambda: broken.save(record))
|
||||
with psycopg.connect(**info()) as db:
|
||||
committed_head = db.execute('SELECT head FROM identity_provisioner.group_journal_meta').fetchone()[0]
|
||||
require(committed_head == checkpoint + 1)
|
||||
require(json.loads((ROOT / 'checkpoint.json').read_text())['head'] == checkpoint)
|
||||
refused(lambda: store().check())
|
||||
return {'lost_checkpoint_ack_retains_pending_reservation': True, 'reopened_uncertainty_refuses_ldap_effects': True,
|
||||
'active_fence_survives_process_reopen': True, 'lost_synthetic_directory_response_retains_postgres_intent': True,
|
||||
'lost_sql_commit_reply_retains_committed_head': True, 'reopened_stale_checkpoint_refuses_continuation': True}
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
require({p.name for p in Path('/sys/class/net').iterdir()} == {'lo'})
|
||||
require(len(Path('/proc/net/route').read_text().splitlines()) == 1)
|
||||
checks = {'only_loopback_no_external_routes': True}
|
||||
checks.update({'bootstrap': bootstrap, 'restart': restart, 'concurrency': concurrency,
|
||||
'fork': fork, 'restored': restored, 'lost-ack': lost_ack}[sys.argv[1]]())
|
||||
print(json.dumps({'status': 'passed', 'checks': checks, 'psycopg_version': psycopg.__version__, 'active_database': info()['dbname']}))
|
||||
except Exception:
|
||||
print(json.dumps({'status': 'failed', 'failure': 'synthetic_pg_oracle_failed', 'phase': sys.argv[1]}))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"base_revision": "6cb43c0cc55212363a5406b02db046e2384a7c40",
|
||||
"base_tree": "615095596348b23ad34da79cd0e8597cdb815c5d",
|
||||
"scope": "source successor with content-bound continuity; no native/canonical admission",
|
||||
"files": [
|
||||
{
|
||||
"path": "docs/service-group-provenance.md",
|
||||
"preimage_sha256": "cd6b45640983b11e29a6bc6278bcb45de8fd8a5ddcad53eb77ab1a1f3f1f1280",
|
||||
"postimage_sha256": "b287e7a85aa2146f35d2bc8473dfd05239ea0179b7b8b186d2e85bc07c7b650c",
|
||||
"preimage_bytes": 7455,
|
||||
"postimage_bytes": 11401
|
||||
},
|
||||
{
|
||||
"path": "identity-provisioner/Containerfile",
|
||||
"preimage_sha256": "c744f39ffb4c0dc2686c3e94d8233bc772b55faae9581557bee17f91c19d9ca6",
|
||||
"postimage_sha256": "31e7d948d4f40efd6950764a3dbb8c410d50ed7a1c13c18e0830a1186f2abafc",
|
||||
"preimage_bytes": 459,
|
||||
"postimage_bytes": 651
|
||||
},
|
||||
{
|
||||
"path": "identity-provisioner/group_provenance.py",
|
||||
"preimage_sha256": "fbd4c88d7682ba3d46c5b325dad1bc4f4a7faac2cd3f6ce9e0f7a89283bf46d4",
|
||||
"postimage_sha256": "405e08eaaab7c21d618dff4db5970607806f32ab6c461effb81c737e32182b10",
|
||||
"preimage_bytes": 8947,
|
||||
"postimage_bytes": 9409
|
||||
},
|
||||
{
|
||||
"path": "identity-provisioner/group_provenance_postgres.py",
|
||||
"preimage_sha256": null,
|
||||
"postimage_sha256": "55282af26f95af66ff8d444d38d0bcbcf3ee58a615adedfff8da5db2b0716889",
|
||||
"preimage_bytes": 0,
|
||||
"postimage_bytes": 13176
|
||||
},
|
||||
{
|
||||
"path": "identity-provisioner/migrations/group_journal_postgres_v1.sql",
|
||||
"preimage_sha256": null,
|
||||
"postimage_sha256": "3cdf82194d6f1e2dd865b0c7e7b8a14447fda8c49151148134a0d40a80fb46ac",
|
||||
"preimage_bytes": 0,
|
||||
"postimage_bytes": 1353
|
||||
},
|
||||
{
|
||||
"path": "identity-provisioner/provisioner.py",
|
||||
"preimage_sha256": "2f8e4368e7155465bbc4070b1019c7105edf43bdc2ad3bbc5aa9bc19dc6ed7a5",
|
||||
"postimage_sha256": "19d1b837cbafeeacb1aa815856cd68091e6702281d72b1d860230fa320e1725b",
|
||||
"preimage_bytes": 31721,
|
||||
"postimage_bytes": 31727
|
||||
},
|
||||
{
|
||||
"path": "identity-provisioner/requirements-postgres.txt",
|
||||
"preimage_sha256": null,
|
||||
"postimage_sha256": "14f1beae54a789d2c880654ca0ce4094c9aa54645e79314b4297c12938ba8877",
|
||||
"preimage_bytes": 0,
|
||||
"postimage_bytes": 450
|
||||
},
|
||||
{
|
||||
"path": "identity-provisioner/tests/test_group_postgres.py",
|
||||
"preimage_sha256": null,
|
||||
"postimage_sha256": "6fdf86b7158ce263b141265346d2910c074a8ae5483417c961053c7792db96c0",
|
||||
"preimage_bytes": 0,
|
||||
"postimage_bytes": 10173
|
||||
}
|
||||
],
|
||||
"patch_sha256": "503e3d0217e6be25df8a58f3968cab7c00cdd9933d0fee2a66ef951fc4aae2b1",
|
||||
"superseded_counter_only": "review-r0"
|
||||
}
|
||||
|
|
@ -0,0 +1,626 @@
|
|||
diff --git a/docs/service-group-provenance.md b/docs/service-group-provenance.md
|
||||
--- a/docs/service-group-provenance.md
|
||||
+++ b/docs/service-group-provenance.md
|
||||
@@ -80,6 +80,64 @@
|
||||
and replay. No arbitrary JSON import, receipt expiry or automatic uncertainty
|
||||
clearing is introduced. Owner reconciliation needs independent evidence.
|
||||
|
||||
+## PostgreSQL source seam (not native admission)
|
||||
+
|
||||
+`PostgresGroupProvenance` implements the same reserve/get/claim-revoke/save/check
|
||||
+journal contract. It is injection-only: the ordinary HTTP server has no
|
||||
+PostgreSQL environment switch and remains without a group store unless the
|
||||
+existing local SQLite configuration is explicitly supplied. `GROUP_POSTGRES_DSN`
|
||||
+or a caller JSON object cannot enable the PostgreSQL path. Production group
|
||||
+operations remain unadmitted and disabled pending the owner callbacks and gates
|
||||
+above; a backend constructor is not an access decision.
|
||||
+
|
||||
+A trusted owner integration must provide an exact `GroupJournalBinding` with
|
||||
+opaque authoritative tenant scope, directory instance, accepted case digest,
|
||||
+source digest, represented principal and journal epoch. There is no guessed
|
||||
+platform-root scope or request-derived tenant fallback. The owner also injects a
|
||||
+renewable private connection-info callback using the existing `rapp-postgres`
|
||||
+consumer/lease contract, and independently durable `JournalContinuity` callbacks.
|
||||
+These callbacks retain/check/advance exact epoch+directory+head+commit digest outside the
|
||||
+PostgreSQL state that might be restored. Neither callback authenticates a caller
|
||||
+or grants a protected side effect. Their native custody, source binding,
|
||||
+revocation and execution bounds still require independent admission. Only the
|
||||
+disposable fixture supplies a test checkpoint implementation here.
|
||||
+
|
||||
+The v1 migration under `identity-provisioner/migrations/` creates tables within
|
||||
+the existing consumer schema; it creates no database/schema/roles, inserts no
|
||||
+epoch and clears no quarantine. Startup does not run migrations or initialize
|
||||
+missing state. The owner must separately admit an epoch/checkpoint after
|
||||
+independent continuity/recovery review; absent or quarantined state refuses.
|
||||
+A restored database with an older head than the independently retained checkpoint
|
||||
+also refuses rather than treating missing later intents as authority to replay.
|
||||
+
|
||||
+PostgreSQL transactions lock the single journal metadata row, commit bounded
|
||||
+receipts/intents and advance the head and exact transition digest with an exactly-one conditional
|
||||
+update. The digest binds the prior marker, canonical body, context, request key
|
||||
+and receipt version; the independent checkpoint must compare/CAS both content
|
||||
+and sequence, never accept a counter-only callback. Divergent restored clones
|
||||
+that commit different transitions at the same counter cannot both be accepted.
|
||||
+Every receipt update also requires its exact context/key/request/version and
|
||||
+increments its version. The active slot spans all contexts in the journal;
|
||||
+it serializes cooperating producer operations, not external LDAP writers.
|
||||
+SQL commit precedes independently durable checkpoint acknowledgment. A lost
|
||||
+commit/acknowledgment, stale version, missing row or continuity mismatch refuses
|
||||
+and poisons that backend object; exception handling cannot automatically rewrite
|
||||
+its journal into success. Known request-binding mismatches remain domain
|
||||
+refusals. Definite before-mutation refusal records can release the active slot
|
||||
+while the original key remains terminal.
|
||||
+
|
||||
+The container source includes a hash-pinned Linux amd64 CPython3.12 psycopg3
|
||||
+closure selected from User Engine's existing lockfile. No image build or package
|
||||
+installation is part of this source proof; build/publication, artifact scanning
|
||||
+and native runtime/dependency admission remain owner work. SQLite is still
|
||||
+local/disposable only. The separate PostgreSQL fixture proposal uses a cached
|
||||
+pinned PostgreSQL image and a cached pinned User Engine Python image, real SQL,
|
||||
+separate-process reopen, concurrency, stale-CAS and actual pg_dump/pg_restore
|
||||
+behind an independently retained test checkpoint. That fixture needs exact
|
||||
+review before Docker execution and does not prove live CNPG HA, TLS/lease
|
||||
+custody, tenant authority, external writer fencing, production retention or
|
||||
+protected session/evidence integration.
|
||||
+
|
||||
## Safety and remaining native acceptance
|
||||
|
||||
Before each directory mutation, durable intent is committed. A successful
|
||||
diff --git a/identity-provisioner/Containerfile b/identity-provisioner/Containerfile
|
||||
--- a/identity-provisioner/Containerfile
|
||||
+++ b/identity-provisioner/Containerfile
|
||||
@@ -3,7 +3,10 @@
|
||||
RUN useradd --create-home --uid 10001 provisioner
|
||||
WORKDIR /app
|
||||
COPY --from=lldap /app/lldap_set_password /app/lldap_set_password
|
||||
-COPY provisioner.py group_provenance.py password_setup.py server.py /app/
|
||||
+COPY requirements-postgres.txt /app/
|
||||
+RUN python -m pip install --no-cache-dir --require-hashes -r /app/requirements-postgres.txt
|
||||
+COPY provisioner.py group_provenance.py group_provenance_postgres.py password_setup.py server.py /app/
|
||||
+COPY migrations/ /app/migrations/
|
||||
USER 10001:10001
|
||||
EXPOSE 8080
|
||||
CMD ["python", "server.py"]
|
||||
diff --git a/identity-provisioner/group_provenance.py b/identity-provisioner/group_provenance.py
|
||||
--- a/identity-provisioner/group_provenance.py
|
||||
+++ b/identity-provisioner/group_provenance.py
|
||||
@@ -11,8 +11,17 @@
|
||||
from pathlib import Path
|
||||
import sqlite3
|
||||
import stat
|
||||
-from typing import Any
|
||||
+from typing import Any, Protocol
|
||||
from urllib.parse import quote
|
||||
+
|
||||
+
|
||||
+class GroupJournalStore(Protocol):
|
||||
+ """Owner-injected persistence only; grants no caller or native authority."""
|
||||
+ def check(self) -> None: ...
|
||||
+ def reserve(self, key: str, binding: str) -> tuple[dict[str, Any], bool]: ...
|
||||
+ def get(self, key: str, binding: str) -> dict[str, Any]: ...
|
||||
+ def claim_revoke(self, key: str, binding: str, delete_if_empty: bool) -> dict[str, Any]: ...
|
||||
+ def save(self, receipt: dict[str, Any]) -> None: ...
|
||||
|
||||
|
||||
class GroupProvenanceUnavailable(RuntimeError):
|
||||
diff --git a/identity-provisioner/group_provenance_postgres.py b/identity-provisioner/group_provenance_postgres.py
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/identity-provisioner/group_provenance_postgres.py
|
||||
@@ -0,0 +1,251 @@
|
||||
+"""Injection-only PostgreSQL implementation of the existing owner journal seam.
|
||||
+
|
||||
+Binding and continuity callbacks are supplied by a trusted owner integration,
|
||||
+never constructed from an HTTP payload. Neither is an authorization decision.
|
||||
+No server environment switch, automatic schema creation or recovery admission.
|
||||
+"""
|
||||
+from __future__ import annotations
|
||||
+
|
||||
+from contextlib import contextmanager
|
||||
+from dataclasses import asdict, dataclass
|
||||
+import hashlib
|
||||
+import json
|
||||
+import re
|
||||
+from typing import Any, Callable, Protocol
|
||||
+from uuid import UUID
|
||||
+
|
||||
+from group_provenance import GroupProvenance, GroupProvenanceUnavailable
|
||||
+
|
||||
+
|
||||
+@dataclass(frozen=True)
|
||||
+class GroupJournalBinding:
|
||||
+ tenant_scope: str
|
||||
+ directory: str
|
||||
+ case_digest: str
|
||||
+ source_digest: str
|
||||
+ principal: str
|
||||
+ epoch: str
|
||||
+
|
||||
+ def __post_init__(self):
|
||||
+ for value in (self.tenant_scope, self.directory, self.principal):
|
||||
+ if not isinstance(value, str) or not 1 <= len(value) <= 256 or any(ord(c) < 32 for c in value):
|
||||
+ raise ValueError("explicit owner journal binding required")
|
||||
+ for value in (self.case_digest, self.source_digest):
|
||||
+ if not isinstance(value, str) or not re.fullmatch(r"sha256:[0-9a-f]{64}", value):
|
||||
+ raise ValueError("exact owner source and case digests required")
|
||||
+ if not isinstance(self.epoch, str) or str(UUID(self.epoch)) != self.epoch:
|
||||
+ raise ValueError("explicit canonical journal epoch required")
|
||||
+
|
||||
+ @property
|
||||
+ def digest(self):
|
||||
+ return hashlib.sha256(json.dumps(asdict(self), sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
+
|
||||
+
|
||||
+def journal_genesis(epoch: str, directory: str) -> str:
|
||||
+ """Deterministic initial marker for explicit owner migration/admission only."""
|
||||
+ return hashlib.sha256(json.dumps({"schema": "nk-group-journal-genesis.v1", "epoch": epoch,
|
||||
+ "directory": directory}, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
+
|
||||
+
|
||||
+class JournalContinuity(Protocol):
|
||||
+ """Existing independently retained owner checkpoint, outside restored SQL state.
|
||||
+
|
||||
+ Implementations compare/advance exact epoch+directory+head+commit digest atomically and
|
||||
+ durably. An in-memory implementation is a fixture only. Missing/stale or
|
||||
+ ambiguous checkpoints must refuse; no callback grants mutation authority.
|
||||
+ """
|
||||
+ def check(self, epoch: str, directory: str, head: int, digest: str) -> bool: ...
|
||||
+ def advance(self, epoch: str, directory: str, previous: int, previous_digest: str, current: int, current_digest: str) -> bool: ...
|
||||
+
|
||||
+
|
||||
+class PostgresGroupProvenance:
|
||||
+ def __init__(self, *, connect: Callable, binding: GroupJournalBinding,
|
||||
+ continuity: JournalContinuity, capacity: int = 256):
|
||||
+ if not callable(connect) or not isinstance(binding, GroupJournalBinding) or not 1 <= capacity <= 4096:
|
||||
+ raise ValueError("explicit owner connector, binding and bounded capacity required")
|
||||
+ if not callable(getattr(continuity, "check", None)) or not callable(getattr(continuity, "advance", None)):
|
||||
+ raise ValueError("independent owner continuity callbacks required")
|
||||
+ self.connect, self.binding, self.continuity, self.capacity = connect, binding, continuity, capacity
|
||||
+ self._poisoned = False
|
||||
+
|
||||
+ def _continuity(self, method, *args):
|
||||
+ try:
|
||||
+ if getattr(self.continuity, method)(self.binding.epoch, self.binding.directory, *args) is not True:
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ except Exception:
|
||||
+ raise GroupProvenanceUnavailable() from None
|
||||
+
|
||||
+ @contextmanager
|
||||
+ def _transaction(self):
|
||||
+ if self._poisoned:
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ connection = None
|
||||
+ tx = None
|
||||
+ try:
|
||||
+ connection = self.connect()
|
||||
+ with connection:
|
||||
+ with connection.cursor() as cursor:
|
||||
+ cursor.execute("SET LOCAL statement_timeout = '5s'")
|
||||
+ cursor.execute("SET LOCAL idle_in_transaction_session_timeout = '15s'")
|
||||
+ cursor.execute("SELECT epoch::text, directory, head, head_digest, quarantined FROM identity_provisioner.group_journal_meta WHERE singleton = true FOR UPDATE")
|
||||
+ meta = cursor.fetchone()
|
||||
+ if not meta or meta[0] != self.binding.epoch or meta[1] != self.binding.directory or meta[4] is not False or type(meta[2]) is not int or meta[2] < 0 or not isinstance(meta[3], str) or not re.fullmatch(r"[0-9a-f]{64}", meta[3]):
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ self._continuity("check", meta[2], meta[3])
|
||||
+ tx = {"cursor": cursor, "head": meta[2], "digest": meta[3], "dirty": False}
|
||||
+ yield tx
|
||||
+ if tx["dirty"]:
|
||||
+ cursor.execute("UPDATE identity_provisioner.group_journal_meta SET head = head + 1, head_digest = %s WHERE singleton = true AND epoch = %s AND directory = %s AND head = %s AND head_digest = %s AND quarantined = false",
|
||||
+ (tx["next_digest"], self.binding.epoch, self.binding.directory, tx["head"], tx["digest"]))
|
||||
+ if cursor.rowcount != 1:
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ # SQL commit precedes the independent checkpoint acknowledgment.
|
||||
+ # A lost acknowledgment refuses before the next directory effect.
|
||||
+ if tx and tx["dirty"]:
|
||||
+ self._continuity("advance", tx["head"], tx["digest"], tx["head"] + 1, tx["next_digest"])
|
||||
+ except ValueError:
|
||||
+ raise
|
||||
+ except GroupProvenanceUnavailable:
|
||||
+ # Caller mismatches are domain refusals. Storage/continuity failures
|
||||
+ # poison this object; durable pending records are never rewritten
|
||||
+ # automatically by an exception handler after ambiguous commit.
|
||||
+ self._poisoned = True
|
||||
+ raise
|
||||
+ except Exception:
|
||||
+ self._poisoned = True
|
||||
+ raise GroupProvenanceUnavailable() from None
|
||||
+ finally:
|
||||
+ if connection is not None:
|
||||
+ try:
|
||||
+ connection.close()
|
||||
+ except Exception:
|
||||
+ self._poisoned = True
|
||||
+ raise GroupProvenanceUnavailable() from None
|
||||
+
|
||||
+ def check(self):
|
||||
+ with self._transaction():
|
||||
+ pass
|
||||
+
|
||||
+ def _read(self, cursor, key, request_binding):
|
||||
+ cursor.execute("SELECT binding, version, body, context_json FROM identity_provisioner.group_receipts WHERE context_digest = %s AND key = %s",
|
||||
+ (self.binding.digest, key))
|
||||
+ row = cursor.fetchone()
|
||||
+ if row is None:
|
||||
+ return None
|
||||
+ if row[0] != request_binding:
|
||||
+ raise ValueError("ensure receipt request binding mismatch")
|
||||
+ if row[3] != json.dumps(asdict(self.binding), sort_keys=True):
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ receipt = GroupProvenance._load(row[2], key, request_binding)
|
||||
+ if type(row[1]) is not int or row[1] < 1 or type(receipt.get("_pg_version")) is not int or receipt.get("_pg_version") != row[1] or receipt.get("_pg_context") != self.binding.digest:
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ return receipt
|
||||
+
|
||||
+ @staticmethod
|
||||
+ def _inputs(key, binding):
|
||||
+ if not isinstance(key, str) or not 1 <= len(key) <= 256 or not isinstance(binding, str) or not re.fullmatch(r"[0-9a-f]{64}", binding):
|
||||
+ raise ValueError("bounded exact journal request binding required")
|
||||
+
|
||||
+ def _active(self, cursor):
|
||||
+ cursor.execute("SELECT 1 FROM identity_provisioner.group_receipts WHERE active = true LIMIT 1")
|
||||
+ if cursor.fetchone():
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+
|
||||
+ def _transition(self, tx, receipt, operation):
|
||||
+ # Bind the exact transition, not merely a counter. Two restored/forked
|
||||
+ # copies that commit different bodies at the same next head cannot both
|
||||
+ # match an independently retained checkpoint after reopening.
|
||||
+ transition = {"schema": "nk-group-journal-transition.v1", "epoch": self.binding.epoch,
|
||||
+ "directory": self.binding.directory, "prior_head": tx["head"], "prior_digest": tx["digest"],
|
||||
+ "head": tx["head"] + 1, "operation": operation, "context": asdict(self.binding),
|
||||
+ "key": receipt["key"], "request_binding": receipt["binding"],
|
||||
+ "version": receipt["_pg_version"], "body": receipt}
|
||||
+ tx["next_digest"] = hashlib.sha256(json.dumps(transition, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
|
||||
+ tx["dirty"] = True
|
||||
+
|
||||
+ def reserve(self, key, binding):
|
||||
+ self._inputs(key, binding)
|
||||
+ with self._transaction() as tx:
|
||||
+ cursor = tx["cursor"]
|
||||
+ receipt = self._read(cursor, key, binding)
|
||||
+ if receipt is not None:
|
||||
+ return receipt, False
|
||||
+ self._active(cursor)
|
||||
+ cursor.execute("SELECT COUNT(*) FROM identity_provisioner.group_receipts")
|
||||
+ if cursor.fetchone()[0] >= self.capacity:
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ receipt = {"key": key, "binding": binding, "state": "pending", "intent": None, "groups": {},
|
||||
+ "created_groups": [], "created_memberships": [], "_pg_version": 1, "_pg_context": self.binding.digest}
|
||||
+ cursor.execute("INSERT INTO identity_provisioner.group_receipts (context_digest, context_json, key, binding, version, state, active, body) VALUES (%s, %s, %s, %s, 1, 'pending', true, %s)",
|
||||
+ (self.binding.digest, json.dumps(asdict(self.binding), sort_keys=True), key, binding, json.dumps(receipt, sort_keys=True)))
|
||||
+ if cursor.rowcount != 1:
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ self._transition(tx, receipt, "reserve")
|
||||
+ return receipt, True
|
||||
+
|
||||
+ def get(self, key, binding):
|
||||
+ self._inputs(key, binding)
|
||||
+ with self._transaction() as tx:
|
||||
+ receipt = self._read(tx["cursor"], key, binding)
|
||||
+ if receipt is None:
|
||||
+ raise ValueError("unknown ensure receipt or request binding mismatch")
|
||||
+ return receipt
|
||||
+
|
||||
+ def _write(self, tx, receipt, operation="save"):
|
||||
+ self._inputs(receipt.get("key"), receipt.get("binding"))
|
||||
+ if receipt.get("_pg_context") != self.binding.digest or type(receipt.get("_pg_version")) is not int or receipt["_pg_version"] < 1:
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ encoded = json.dumps(receipt, sort_keys=True)
|
||||
+ GroupProvenance._load(encoded, receipt["key"], receipt["binding"])
|
||||
+ updated = json.loads(encoded)
|
||||
+ updated["_pg_version"] += 1
|
||||
+ active = True if receipt["state"] in {"pending", "revoking", "uncertain"} else None
|
||||
+ tx["cursor"].execute("UPDATE identity_provisioner.group_receipts SET state = %s, active = %s, body = %s, version = version + 1 WHERE context_digest = %s AND key = %s AND binding = %s AND version = %s",
|
||||
+ (receipt["state"], active, json.dumps(updated, sort_keys=True), self.binding.digest, receipt["key"], receipt["binding"], receipt["_pg_version"]))
|
||||
+ if tx["cursor"].rowcount != 1:
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ self._transition(tx, updated, operation)
|
||||
+ return updated
|
||||
+
|
||||
+ def save(self, receipt):
|
||||
+ with self._transaction() as tx:
|
||||
+ updated = self._write(tx, receipt)
|
||||
+ receipt.clear()
|
||||
+ receipt.update(updated)
|
||||
+
|
||||
+ def claim_revoke(self, key, binding, delete_if_empty):
|
||||
+ self._inputs(key, binding)
|
||||
+ if not isinstance(delete_if_empty, bool):
|
||||
+ raise ValueError("delete_if_empty must be boolean")
|
||||
+ with self._transaction() as tx:
|
||||
+ receipt = self._read(tx["cursor"], key, binding)
|
||||
+ if receipt is None:
|
||||
+ raise ValueError("unknown ensure receipt or request binding mismatch")
|
||||
+ if receipt["state"] == "revoked":
|
||||
+ if receipt["delete_if_empty"] != delete_if_empty:
|
||||
+ raise ValueError("completed rollback options cannot change")
|
||||
+ return receipt
|
||||
+ if receipt["state"] != "ensured":
|
||||
+ raise GroupProvenanceUnavailable()
|
||||
+ self._active(tx["cursor"])
|
||||
+ receipt.update(state="revoking", delete_if_empty=delete_if_empty)
|
||||
+ updated = self._write(tx, receipt, "claim_revoke")
|
||||
+ return updated
|
||||
+
|
||||
+
|
||||
+def psycopg_connector(*, connection_info: Callable[[], dict[str, Any]]):
|
||||
+ """Owner-provided renewable lease values stay in memory; never env/argv/logs.
|
||||
+
|
||||
+ No connection happens here. Lazy import keeps disabled group endpoints free
|
||||
+ of a runtime dependency. The admitted owner supplies TLS/host/db/role facts.
|
||||
+ """
|
||||
+ if not callable(connection_info):
|
||||
+ raise ValueError("owner connection-info callback required")
|
||||
+ def connect():
|
||||
+ try:
|
||||
+ import psycopg
|
||||
+ values = dict(connection_info())
|
||||
+ return psycopg.connect(**values, connect_timeout=5)
|
||||
+ except Exception:
|
||||
+ raise GroupProvenanceUnavailable() from None
|
||||
+ return connect
|
||||
diff --git a/identity-provisioner/migrations/group_journal_postgres_v1.sql b/identity-provisioner/migrations/group_journal_postgres_v1.sql
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/identity-provisioner/migrations/group_journal_postgres_v1.sql
|
||||
@@ -0,0 +1,23 @@
|
||||
+-- Run only by the admitted migration role in its existing owner schema.
|
||||
+-- Does not create a database/schema/credential, admit an epoch or clear quarantine.
|
||||
+CREATE TABLE identity_provisioner.group_journal_meta (
|
||||
+ singleton boolean PRIMARY KEY CHECK (singleton = true),
|
||||
+ epoch uuid NOT NULL,
|
||||
+ directory text NOT NULL CHECK (octet_length(directory) BETWEEN 1 AND 1024),
|
||||
+ head bigint NOT NULL CHECK (head >= 0),
|
||||
+ head_digest text NOT NULL CHECK (head_digest ~ '^[0-9a-f]{64}$'),
|
||||
+ quarantined boolean NOT NULL DEFAULT true
|
||||
+);
|
||||
+CREATE TABLE identity_provisioner.group_receipts (
|
||||
+ context_digest text NOT NULL CHECK (context_digest ~ '^[0-9a-f]{64}$'),
|
||||
+ context_json text NOT NULL CHECK (octet_length(context_json) <= 16384),
|
||||
+ key text NOT NULL CHECK (char_length(key) BETWEEN 1 AND 256),
|
||||
+ binding text NOT NULL CHECK (binding ~ '^[0-9a-f]{64}$'),
|
||||
+ version bigint NOT NULL CHECK (version >= 1),
|
||||
+ state text NOT NULL CHECK (state IN ('pending','ensured','revoking','revoked','uncertain','refused')),
|
||||
+ active boolean UNIQUE CHECK (active IS NULL OR active = true),
|
||||
+ body text NOT NULL CHECK (octet_length(body) <= 1048576),
|
||||
+ PRIMARY KEY (context_digest, key)
|
||||
+);
|
||||
+-- Epoch insertion, initial continuity checkpoint and admission are separate
|
||||
+-- owner actions; this migration leaves all operations refused without them.
|
||||
diff --git a/identity-provisioner/provisioner.py b/identity-provisioner/provisioner.py
|
||||
--- a/identity-provisioner/provisioner.py
|
||||
+++ b/identity-provisioner/provisioner.py
|
||||
@@ -11,7 +11,7 @@
|
||||
from urllib.error import HTTPError, URLError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
-from group_provenance import GroupProvenance, GroupProvenanceUnavailable
|
||||
+from group_provenance import GroupJournalStore, GroupProvenanceUnavailable
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -64,7 +64,7 @@
|
||||
opener: Callable = urlopen,
|
||||
auth_rejected_cooldown: float = 30.0,
|
||||
clock: Callable[[], float] = time.monotonic,
|
||||
- group_provenance: GroupProvenance | None = None,
|
||||
+ group_provenance: GroupJournalStore | None = None,
|
||||
) -> None:
|
||||
self.group_provenance = group_provenance
|
||||
self.base_url = base_url.rstrip("/")
|
||||
@@ -243,7 +243,7 @@
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
- def _group_store(self) -> GroupProvenance:
|
||||
+ def _group_store(self) -> GroupJournalStore:
|
||||
store = self.group_provenance
|
||||
if store is None:
|
||||
raise GroupProvenanceUnavailable()
|
||||
diff --git a/identity-provisioner/requirements-postgres.txt b/identity-provisioner/requirements-postgres.txt
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/identity-provisioner/requirements-postgres.txt
|
||||
@@ -0,0 +1,5 @@
|
||||
+# Linux amd64 CPython3.12 runtime closure from user-engine/uv.lock.
|
||||
+# Build-only dependency resolution; no packages are installed at service startup.
|
||||
+psycopg==3.3.5 --hash=sha256:ce5aa5cdb4f9379f00f487590e5890bfa7df9a164648c969ffa628505e21af4e
|
||||
+psycopg-binary==3.3.5 --hash=sha256:682a17a57415c3ca1731eec018ed031f012ffcb81ba74806eb219cb396065672
|
||||
+typing-extensions==4.16.0 --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8
|
||||
diff --git a/identity-provisioner/tests/test_group_postgres.py b/identity-provisioner/tests/test_group_postgres.py
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/identity-provisioner/tests/test_group_postgres.py
|
||||
@@ -0,0 +1,192 @@
|
||||
+"""Binding, CAS and ambiguous-commit refusal tests; native SQL proof is separate."""
|
||||
+from dataclasses import replace
|
||||
+import json
|
||||
+from unittest.mock import Mock
|
||||
+import unittest
|
||||
+
|
||||
+from group_provenance import GroupProvenanceUnavailable
|
||||
+from group_provenance_postgres import GroupJournalBinding, PostgresGroupProvenance
|
||||
+from server import configured_group_provenance
|
||||
+from unittest.mock import patch
|
||||
+import os
|
||||
+
|
||||
+EPOCH = '41fbb0ed-ea3a-4682-996b-e9c94a6aa9c6'
|
||||
+BINDING = GroupJournalBinding('tenant:fixture:one', 'synthetic-directory', 'sha256:' + '1' * 64,
|
||||
+ 'sha256:' + '2' * 64, 'synthetic-owner', EPOCH)
|
||||
+REQUEST = '3' * 64
|
||||
+HEAD_DIGEST = 'a' * 64
|
||||
+META = (EPOCH, 'synthetic-directory', 7, HEAD_DIGEST, False)
|
||||
+
|
||||
+
|
||||
+def fixture(rows=(), rowcount=1, commit_error=None):
|
||||
+ cursor = Mock()
|
||||
+ cursor.__enter__ = Mock(return_value=cursor)
|
||||
+ cursor.__exit__ = Mock(return_value=False)
|
||||
+ cursor.fetchone.side_effect = rows
|
||||
+ cursor.rowcount = rowcount
|
||||
+ connection = Mock()
|
||||
+ connection.__enter__ = Mock(return_value=connection)
|
||||
+ connection.__exit__ = Mock(return_value=False, side_effect=commit_error)
|
||||
+ connection.cursor.return_value = cursor
|
||||
+ connector = Mock(return_value=connection)
|
||||
+ continuity = Mock()
|
||||
+ continuity.check.return_value = True
|
||||
+ continuity.advance.return_value = True
|
||||
+ store = PostgresGroupProvenance(connect=connector, binding=BINDING, continuity=continuity)
|
||||
+ return store, connector, connection, cursor, continuity
|
||||
+
|
||||
+
|
||||
+def receipt(version=4):
|
||||
+ return {'key': 'synthetic-operation-key', 'binding': REQUEST, 'state': 'pending', 'intent': None,
|
||||
+ 'groups': {}, 'created_groups': [], 'created_memberships': [],
|
||||
+ '_pg_version': version, '_pg_context': BINDING.digest}
|
||||
+
|
||||
+
|
||||
+class PostgresSourceTests(unittest.TestCase):
|
||||
+ def test_explicit_binding_and_continuity_required_without_connecting(self):
|
||||
+ connector = Mock()
|
||||
+ for binding, continuity in ((None, Mock()), (BINDING, None), ({'tenant_scope': 'caller-json'}, Mock())):
|
||||
+ with self.assertRaises(ValueError):
|
||||
+ PostgresGroupProvenance(connect=connector, binding=binding, continuity=continuity)
|
||||
+ connector.assert_not_called()
|
||||
+ for change in ({'tenant_scope': ''}, {'principal': ''}, {'case_digest': 'unsigned'}, {'epoch': 'default'}):
|
||||
+ with self.assertRaises(ValueError):
|
||||
+ replace(BINDING, **change)
|
||||
+ self.assertNotEqual(BINDING.digest, replace(BINDING, principal='another-owner').digest)
|
||||
+ self.assertNotEqual(BINDING.digest, replace(BINDING, tenant_scope='another-explicit-scope').digest)
|
||||
+
|
||||
+ def test_missing_environment_callback_path_keeps_production_disabled(self):
|
||||
+ with patch.dict(os.environ, {'GROUP_POSTGRES_DSN': 'synthetic-only', 'GROUP_PROVENANCE_POSTGRES': 'true'}, clear=True):
|
||||
+ self.assertIsNone(configured_group_provenance())
|
||||
+
|
||||
+ def test_checkpoint_mismatch_poisoned_without_receipt_query_or_effect(self):
|
||||
+ store, connect, _, cursor, checkpoint = fixture([META])
|
||||
+ checkpoint.check.return_value = False
|
||||
+ with self.assertRaises(GroupProvenanceUnavailable):
|
||||
+ store.reserve('synthetic-operation-key', REQUEST)
|
||||
+ self.assertFalse(any('group_receipts' in c.args[0] for c in cursor.execute.call_args_list))
|
||||
+ with self.assertRaises(GroupProvenanceUnavailable):
|
||||
+ store.check()
|
||||
+ self.assertEqual(1, connect.call_count)
|
||||
+ checkpoint.advance.assert_not_called()
|
||||
+
|
||||
+ def test_restored_epoch_or_quarantine_refused(self):
|
||||
+ for meta in ((EPOCH, 'other-directory', 7, HEAD_DIGEST, False), (EPOCH, 'synthetic-directory', 7, HEAD_DIGEST, True),
|
||||
+ ('98aa2b6d-a32a-44a5-a89f-b2480befc00e', 'synthetic-directory', 7, HEAD_DIGEST, False), None):
|
||||
+ store, _, _, _, checkpoint = fixture([meta])
|
||||
+ with self.assertRaises(GroupProvenanceUnavailable):
|
||||
+ store.check()
|
||||
+ checkpoint.check.assert_not_called()
|
||||
+
|
||||
+ def test_exactly_one_version_update_required_before_checkpoint_advance(self):
|
||||
+ store, _, _, cursor, checkpoint = fixture([META], rowcount=0)
|
||||
+ original = receipt()
|
||||
+ with self.assertRaises(GroupProvenanceUnavailable):
|
||||
+ store.save(original)
|
||||
+ query, params = next(c.args for c in cursor.execute.call_args_list if c.args[0].startswith('UPDATE identity_provisioner.group_receipts'))
|
||||
+ self.assertIn('AND version = %s', query)
|
||||
+ self.assertEqual(4, params[-1])
|
||||
+ self.assertEqual(4, original['_pg_version'])
|
||||
+ checkpoint.advance.assert_not_called()
|
||||
+
|
||||
+ def test_success_acknowledges_commit_and_updates_receipt_version(self):
|
||||
+ store, _, connection, _, checkpoint = fixture([META])
|
||||
+ original = receipt()
|
||||
+ store.save(original)
|
||||
+ self.assertEqual(5, original['_pg_version'])
|
||||
+ connection.__exit__.assert_called_once()
|
||||
+ args = checkpoint.advance.call_args.args
|
||||
+ self.assertEqual((EPOCH, 'synthetic-directory', 7, HEAD_DIGEST, 8), args[:5])
|
||||
+ self.assertRegex(args[5], r'^[0-9a-f]{64}$')
|
||||
+ self.assertNotEqual(HEAD_DIGEST, args[5])
|
||||
+ connection.close.assert_called_once()
|
||||
+
|
||||
+ def test_connection_close_failure_is_sanitized_and_stops_continuation(self):
|
||||
+ store, _, connection, _, _ = fixture([META])
|
||||
+ connection.close.side_effect = OSError('synthetic-private-diagnostic')
|
||||
+ with self.assertRaisesRegex(GroupProvenanceUnavailable, '^group_provenance_unavailable$'):
|
||||
+ store.check()
|
||||
+ self.assertTrue(store._poisoned)
|
||||
+
|
||||
+ def test_commit_failure_never_advances_owner_checkpoint_or_receipt(self):
|
||||
+ store, _, _, _, checkpoint = fixture([META], commit_error=OSError('synthetic-secret-diagnostic'))
|
||||
+ original = receipt()
|
||||
+ with self.assertRaisesRegex(GroupProvenanceUnavailable, '^group_provenance_unavailable$'):
|
||||
+ store.save(original)
|
||||
+ self.assertEqual(4, original['_pg_version'])
|
||||
+ checkpoint.advance.assert_not_called()
|
||||
+
|
||||
+ def test_lost_checkpoint_acknowledgment_refuses_then_poisoned(self):
|
||||
+ store, connect, _, _, checkpoint = fixture([META])
|
||||
+ checkpoint.advance.side_effect = OSError('synthetic-secret-diagnostic')
|
||||
+ original = receipt()
|
||||
+ with self.assertRaisesRegex(GroupProvenanceUnavailable, '^group_provenance_unavailable$'):
|
||||
+ store.save(original)
|
||||
+ self.assertEqual(4, original['_pg_version'])
|
||||
+ with self.assertRaises(GroupProvenanceUnavailable):
|
||||
+ store.save(original)
|
||||
+ self.assertEqual(1, connect.call_count)
|
||||
+
|
||||
+ def test_cross_case_body_or_version_cannot_be_imported(self):
|
||||
+ for body in (dict(receipt(), _pg_context='0' * 64), dict(receipt(), _pg_version=6)):
|
||||
+ store, _, _, _, _ = fixture([META, (REQUEST, 4, json.dumps(body), json.dumps(BINDING.__dict__, sort_keys=True))])
|
||||
+ with self.assertRaises(GroupProvenanceUnavailable):
|
||||
+ store.get('synthetic-operation-key', REQUEST)
|
||||
+
|
||||
+ def test_same_counter_divergent_body_or_context_yields_distinct_marker(self):
|
||||
+ store, *_ = fixture()
|
||||
+ markers = []
|
||||
+ for change in ({}, {'intent': {'operation': 'different'}}, {'key': 'another-exact-key'}, {'_pg_version': 5}):
|
||||
+ tx = {'head': 7, 'digest': HEAD_DIGEST}
|
||||
+ store._transition(tx, dict(receipt(), **change), 'save')
|
||||
+ markers.append(tx['next_digest'])
|
||||
+ self.assertEqual(4, len(set(markers)))
|
||||
+ tx = {'head': 7, 'digest': 'b' * 64}
|
||||
+ store._transition(tx, receipt(), 'save')
|
||||
+ self.assertNotIn(tx['next_digest'], markers)
|
||||
+ foreign, *_ = fixture()
|
||||
+ foreign.binding = replace(BINDING, principal='another-owner')
|
||||
+ tx = {'head': 7, 'digest': HEAD_DIGEST}
|
||||
+ foreign._transition(tx, receipt(), 'save')
|
||||
+ self.assertNotIn(tx['next_digest'], markers)
|
||||
+
|
||||
+ def test_reopened_loser_clone_at_same_head_refuses_different_marker(self):
|
||||
+ # Both DBs report head8. The independent callback accepts only winner
|
||||
+ # digest; loser must be refused before loading any receipt or effect.
|
||||
+ loser_digest = 'b' * 64
|
||||
+ store, _, _, cursor, checkpoint = fixture([(EPOCH, 'synthetic-directory', 8, loser_digest, False)])
|
||||
+ checkpoint.check.side_effect = lambda epoch, directory, head, digest: (head, digest) == (8, 'c' * 64)
|
||||
+ with self.assertRaises(GroupProvenanceUnavailable):
|
||||
+ store.check()
|
||||
+ checkpoint.check.assert_called_once_with(EPOCH, 'synthetic-directory', 8, loser_digest)
|
||||
+ self.assertFalse(any('group_receipts' in c.args[0] for c in cursor.execute.call_args_list))
|
||||
+
|
||||
+ def test_existing_request_mismatch_is_refused_not_rebound(self):
|
||||
+ store, _, _, _, checkpoint = fixture([META, ('0' * 64, 4, '{}', '{}')])
|
||||
+ with self.assertRaisesRegex(ValueError, 'binding mismatch'):
|
||||
+ store.get('synthetic-operation-key', REQUEST)
|
||||
+ checkpoint.advance.assert_not_called()
|
||||
+ self.assertFalse(store._poisoned)
|
||||
+
|
||||
+ def test_active_fence_capacity_refusal_and_no_insert(self):
|
||||
+ for rows in ([META, None, (1,)], [META, None, None, (256,)]):
|
||||
+ store, _, _, cursor, checkpoint = fixture(rows)
|
||||
+ with self.assertRaises(GroupProvenanceUnavailable):
|
||||
+ store.reserve('synthetic-operation-key', REQUEST)
|
||||
+ self.assertFalse(any('INSERT' in c.args[0] for c in cursor.execute.call_args_list))
|
||||
+ checkpoint.advance.assert_not_called()
|
||||
+
|
||||
+ def test_new_reservation_retains_bound_context_and_write_intent(self):
|
||||
+ store, _, _, cursor, checkpoint = fixture([META, None, None, (0,)])
|
||||
+ created, fresh = store.reserve('synthetic-operation-key', REQUEST)
|
||||
+ self.assertTrue(fresh)
|
||||
+ self.assertEqual('pending', created['state'])
|
||||
+ self.assertEqual(BINDING.digest, created['_pg_context'])
|
||||
+ insert = next(c for c in cursor.execute.call_args_list if c.args[0].startswith('INSERT'))
|
||||
+ self.assertIn('context_json', insert.args[0])
|
||||
+ self.assertEqual(BINDING.tenant_scope, json.loads(insert.args[1][1])['tenant_scope'])
|
||||
+ checkpoint.advance.assert_called_once()
|
||||
+
|
||||
+
|
||||
+if __name__ == '__main__':
|
||||
+ unittest.main()
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import contextlib
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tarfile
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
spec = importlib.util.spec_from_file_location('group_fixture_driver', HERE / 'driver.py')
|
||||
f = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(f)
|
||||
|
||||
|
||||
class PlanTests(unittest.TestCase):
|
||||
def test_default_and_explicit_plan_do_not_load_containment_or_generate_material(self):
|
||||
for args in ([], ['--plan']):
|
||||
with patch.object(sys, 'argv', ['fixture', *args]), patch.object(f, 'load_containment') as load, patch.object(f, 'execute') as execute, patch.object(f.secrets, 'token_hex') as generate, contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
self.assertEqual(0, f.main())
|
||||
self.assertEqual('prepared_not_executed_not_approved', json.loads(out.getvalue())['status'])
|
||||
load.assert_not_called(); execute.assert_not_called(); generate.assert_not_called()
|
||||
|
||||
def test_wrong_manifest_digest_refuses_before_loading_helper(self):
|
||||
with patch.object(sys, 'argv', ['fixture', '--execute-reviewed-fixture', '--expected-manifest-sha256', '0' * 64]), patch.object(f, 'load_containment') as load:
|
||||
with self.assertRaises(RuntimeError):
|
||||
f.main()
|
||||
load.assert_not_called()
|
||||
|
||||
def test_manifest_exact_source_closure_and_images(self):
|
||||
manifest = f.reviewed_manifest(hashlib.sha256(f.MANIFEST.read_bytes()).hexdigest())
|
||||
self.assertEqual('unix:///var/run/docker.sock', manifest['fixed_endpoint'])
|
||||
self.assertEqual('never', manifest['pull_policy'])
|
||||
self.assertEqual('network-none', manifest['network'])
|
||||
self.assertEqual(0, manifest['limits']['host_ports'])
|
||||
self.assertEqual(0, manifest['limits']['host_mounts'])
|
||||
self.assertEqual(2, manifest['limits']['max_service_containers'])
|
||||
self.assertIn('sha256:', manifest['postgres_image'])
|
||||
self.assertIn('sha256:', manifest['python_image'])
|
||||
|
||||
def test_exact_fresh_names_no_host_network_ports_or_mounts(self):
|
||||
manifest = json.loads(f.MANIFEST.read_text())
|
||||
session = f.session_plan(manifest, 'f' * 32)
|
||||
self.assertEqual(2, len(set(session['containers'])))
|
||||
for name in session['containers']:
|
||||
args = f.base_args(session, name, 'none')
|
||||
self.assertIn('--pull=never', args)
|
||||
self.assertIn('--read-only', args)
|
||||
self.assertIn('--log-driver', args)
|
||||
self.assertIn('net-kingdom.recovery-run=' + 'f' * 32, args)
|
||||
self.assertFalse({'--publish', '-p', '--volume', '-v', '--privileged', '--network=host'} & set(args))
|
||||
with self.assertRaises(RuntimeError):
|
||||
f.session_plan(manifest, 'not-a-fresh-run')
|
||||
|
||||
def test_tar_private_only_generated_named_material(self):
|
||||
raw = f.tar_bytes({'password': b'synthetic-only', 'lldap.toml': b'synthetic-config'})
|
||||
with tarfile.open(fileobj=io.BytesIO(raw)) as archive:
|
||||
self.assertTrue(all(member.uid == member.gid == 1000 and member.mode == 0o600 for member in archive))
|
||||
for path in ('../outside', '/absolute', 'path/../../escape'):
|
||||
with self.assertRaises(RuntimeError):
|
||||
f.tar_bytes({path: b'synthetic'})
|
||||
|
||||
def test_failure_still_invokes_existing_exact_cleanup(self):
|
||||
manifest = json.loads(f.MANIFEST.read_text())
|
||||
adapter = Mock()
|
||||
adapter.command.side_effect = RuntimeError('synthetic transport fault')
|
||||
result = {'status': 'failed'}
|
||||
with self.assertRaises(RuntimeError):
|
||||
f.execute(manifest, adapter, result)
|
||||
adapter.cleanup.assert_called_once()
|
||||
session = adapter.cleanup.call_args[0][0]
|
||||
self.assertEqual(session['containers'], result['session']['containers'])
|
||||
self.assertTrue(result['daemon_request_uncertain'])
|
||||
self.assertEqual('failed', result['status'])
|
||||
|
||||
def test_every_declared_source_input_exists_and_schema_does_not_admit_epoch(self):
|
||||
source = HERE.parent / 'postimage/identity-provisioner'
|
||||
for name in ('provisioner.py', 'group_provenance.py', 'group_provenance_postgres.py', 'migrations/group_journal_postgres_v1.sql'):
|
||||
self.assertTrue((source / name).is_file())
|
||||
self.assertTrue((HERE / 'test_service_groups.py').is_file())
|
||||
schema = (source / 'migrations/group_journal_postgres_v1.sql').read_text()
|
||||
self.assertNotIn('INSERT INTO', schema)
|
||||
self.assertIn('DEFAULT true', schema)
|
||||
self.assertIn('active boolean UNIQUE', schema)
|
||||
|
||||
def test_probe_syntax_and_no_network_expansion(self):
|
||||
compile((HERE / 'probe.py').read_text(), str(HERE / 'probe.py'), 'exec')
|
||||
compile(f.IMPORT, 'private-import', 'exec')
|
||||
self.assertNotIn('subprocess', (HERE / 'probe.py').read_text())
|
||||
self.assertIn("'host': '127.0.0.1'", (HERE / 'probe.py').read_text())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
import unittest
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from group_provenance import GroupProvenance
|
||||
|
||||
from provisioner import LLDAPProvisioner, dispatch
|
||||
|
||||
|
||||
class Directory(LLDAPProvisioner):
|
||||
"""In-memory LLDAP: users, groups and memberships."""
|
||||
|
||||
def __init__(self):
|
||||
self.storage = tempfile.TemporaryDirectory()
|
||||
self.group_provenance = GroupProvenance(str(Path(self.storage.name) / "groups.db"), initialize=True)
|
||||
self.users = {"tegwick", "agent-codex"}
|
||||
self.groups = {"lldap_admin": 1, "tenant:trial:one:users": 2, "forge-admins": 3}
|
||||
self.members = {1: {"tegwick"}, 2: set(), 3: set()}
|
||||
self.calls = []
|
||||
self.drop_add = False
|
||||
|
||||
def _login(self):
|
||||
return "test-token"
|
||||
|
||||
def _directory(self, token):
|
||||
return ([{"id": u} for u in sorted(self.users)],
|
||||
[{"id": i, "displayName": n} for n, i in self.groups.items()])
|
||||
|
||||
def _user(self, token, subject):
|
||||
return {"id": subject, "groups": [{"id": i, "displayName": n} for n, i in self.groups.items()
|
||||
if subject in self.members[i]]}
|
||||
|
||||
def _ensure_group(self, token, groups, name):
|
||||
if name not in self.groups:
|
||||
self.calls.append(("create", name))
|
||||
self.groups[name] = max(self.groups.values()) + 1
|
||||
self.members[self.groups[name]] = set()
|
||||
return self.groups[name]
|
||||
|
||||
def _add_group(self, token, username, group_id):
|
||||
self.calls.append(("add", username, group_id))
|
||||
if not self.drop_add:
|
||||
self.members[group_id].add(username)
|
||||
|
||||
def _remove_group(self, token, username, group_id):
|
||||
self.calls.append(("remove", username, group_id))
|
||||
self.members[group_id].discard(username)
|
||||
|
||||
def _group_members(self, token, group_id):
|
||||
return sorted(self.members[group_id])
|
||||
|
||||
def _gql(self, token, query, variables):
|
||||
if "addUserToGroup" in query:
|
||||
self._add_group(token, variables["userId"], variables["groupId"])
|
||||
return {"addUserToGroup": {"ok": True}}
|
||||
if "deleteGroup" in query:
|
||||
self.calls.append(("delete", variables["id"]))
|
||||
name = next(n for n, i in self.groups.items() if i == variables["id"])
|
||||
del self.groups[name]
|
||||
return {"deleteGroup": {"ok": True}}
|
||||
raise AssertionError(query)
|
||||
|
||||
|
||||
def req(**kw):
|
||||
p = {"groups": ["forge-admins", "forge-coulomb-owners", "forge-coulomb-readers"],
|
||||
"members": {"forge-admins": ["tegwick"], "forge-coulomb-owners": ["tegwick"]},
|
||||
"idempotency_key": "forgejo-netkingdom-oidc-1", "correlation_id": "test"}
|
||||
p.update(kw)
|
||||
return p
|
||||
|
||||
|
||||
class EnsureTests(unittest.TestCase):
|
||||
def test_creates_missing_groups_and_adds_members(self):
|
||||
d = Directory()
|
||||
r = dispatch(d, "/v1/groups/ensure", req())
|
||||
self.assertEqual("groups_ensured", r.status)
|
||||
self.assertFalse(r.resumed)
|
||||
self.assertEqual({"forge-coulomb-owners", "forge-coulomb-readers"},
|
||||
{c[1] for c in d.calls if c[0] == "create"})
|
||||
self.assertIn("tegwick", d.members[d.groups["forge-admins"]])
|
||||
self.assertIn("tegwick", d.members[d.groups["forge-coulomb-owners"]])
|
||||
|
||||
def test_idempotent_and_reports_resumed(self):
|
||||
d = Directory()
|
||||
dispatch(d, "/v1/groups/ensure", req())
|
||||
d.calls.clear()
|
||||
r = dispatch(d, "/v1/groups/ensure", req())
|
||||
self.assertTrue(r.resumed)
|
||||
self.assertEqual([], [c for c in d.calls if c[0] == "create"])
|
||||
|
||||
def test_never_removes_other_memberships(self):
|
||||
d = Directory()
|
||||
d.members[3].add("agent-codex")
|
||||
dispatch(d, "/v1/groups/ensure", req())
|
||||
self.assertIn("agent-codex", d.members[3])
|
||||
self.assertEqual([], [c for c in d.calls if c[0] == "remove"])
|
||||
|
||||
def test_reserved_and_invalid_requests_change_nothing(self):
|
||||
bad = [req(groups=["lldap_admin"]), req(groups=["tenant-x"]), req(groups=["netkingdom-suspended"]),
|
||||
req(groups=["Forge Admins"]), req(groups=[]),
|
||||
req(members={"not-listed": ["tegwick"]}),
|
||||
req(members={"forge-admins": ["Not A User"]}),
|
||||
req(members={"forge-admins": ["nobody"]})]
|
||||
for payload in bad:
|
||||
d = Directory()
|
||||
with self.assertRaises(ValueError, msg=payload):
|
||||
dispatch(d, "/v1/groups/ensure", payload)
|
||||
self.assertEqual([], d.calls, payload)
|
||||
|
||||
def test_failed_readback_never_reports_success(self):
|
||||
d = Directory()
|
||||
d.drop_add = True
|
||||
with self.assertRaises(RuntimeError):
|
||||
dispatch(d, "/v1/groups/ensure", req())
|
||||
|
||||
|
||||
class RevokeTests(unittest.TestCase):
|
||||
def test_removes_only_listed_memberships_and_keeps_groups_by_default(self):
|
||||
d = Directory()
|
||||
d.members[3].add("agent-codex")
|
||||
dispatch(d, "/v1/groups/ensure", req())
|
||||
r = dispatch(d, "/v1/groups/revoke", req(ensure_idempotency_key=req()["idempotency_key"], ensure_correlation_id=req()["correlation_id"]))
|
||||
self.assertEqual("groups_revoked", r.status)
|
||||
self.assertEqual({"agent-codex"}, d.members[d.groups["forge-admins"]])
|
||||
self.assertIn("forge-coulomb-owners", d.groups)
|
||||
|
||||
def test_deletes_only_empty_listed_groups_when_asked(self):
|
||||
d = Directory()
|
||||
d.members[3].add("agent-codex")
|
||||
dispatch(d, "/v1/groups/ensure", req())
|
||||
r = dispatch(d, "/v1/groups/revoke", req(delete_if_empty=True, ensure_idempotency_key=req()["idempotency_key"], ensure_correlation_id=req()["correlation_id"]))
|
||||
self.assertEqual("groups_revoked_and_deleted", r.status)
|
||||
self.assertIn("forge-admins", d.groups)
|
||||
self.assertNotIn("forge-coulomb-owners", d.groups)
|
||||
self.assertNotIn("forge-coulomb-readers", d.groups)
|
||||
self.assertIn("lldap_admin", d.groups)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
# Prepared disposable PostgreSQL compatibility fixture
|
||||
|
||||
Status: prepared and unit checked; not executed or approved. Default
|
||||
`python3 driver.py --plan` reads only the manifest, without loading containment,
|
||||
generating material or calling Docker. Execution requires independent review of
|
||||
the exact manifest/source closure and explicit execution flag/digest. The flag
|
||||
and fixture files confer no authority.
|
||||
|
||||
The fixture reuses the existing `isolated-keycape.py` command/environment guard,
|
||||
containment inspector and exact name/label cleanup. A network-none PostgreSQL
|
||||
anchor and a pinned cached User Engine Python helper share only loopback. Both
|
||||
are nonroot/read-only/capability-free, with private bounded tmpfs, no host ports,
|
||||
mounts or Docker socket, and no pulls/builds/packages. Generated admin/runtime
|
||||
passwords enter through stdin only. Image-specific nonroot UID999/Python/psycopg
|
||||
compatibility is an explicit precondition checked by execution; no cache or
|
||||
runtime claim is made during preparation. Any incompatible image/anonymous
|
||||
volume/error fails closed and still invokes exact cleanup.
|
||||
|
||||
Six sequential Python phases use the actual candidate SQL backend and its
|
||||
normal provisioner path with the **existing synthetic LDAP Directory**. Bootstrap
|
||||
runs the real migration on generated state, checks runtime DDL denial and exact
|
||||
created-only inventories. A separate process reopens and replays/revokes while
|
||||
preserving the complete synthetic prestate. Concurrent reservations must yield
|
||||
one original receipt; stale row CAS cannot advance its head. Two actual database
|
||||
clones then pass the same old checkpoint check concurrently and commit different
|
||||
receipts at the same next counter. Only one external digest CAS may win; a fresh
|
||||
backend on the losing clone must refuse even at that same counter. Lost checkpoint
|
||||
acknowledgment, lost synthetic directory response and a lost reply after actual
|
||||
SQL commit must retain pending/uncertain state and refuse continuation. An actual
|
||||
pg_dump taken after bootstrap is then pg_restored after later journal changes;
|
||||
the external test checkpoint survives. Runtime SQL SELECT must work and show an
|
||||
older restored head before the candidate's restore-quarantine refusal oracle can
|
||||
pass. This prevents an unrelated connection/ACL failure from masquerading as
|
||||
restore protection.
|
||||
|
||||
All fresh names/labels are registered before create. `finally` uses the existing
|
||||
cleanup helper for both resources, requires successful exact absence inventories
|
||||
and retains any Docker uncertainty as failed. PID1 lifetimes and RPC/execution
|
||||
limits are bounded in the manifest. This is not a parent-death/reaper proof.
|
||||
Only safe booleans, public image/source/version pins and cleanup duties leave in
|
||||
a private `/tmp/nk48-postgres-native-review-*/result.json`; synthetic backup bytes
|
||||
are bounded in host memory and never written as an archive file.
|
||||
|
||||
The test checkpoint implementation belongs only to this fixture. Its independent
|
||||
file survives PostgreSQL restore and uses file locking/fsync; it is not offered
|
||||
as native custody, case authority, a journal-recovery API or an evidence broker.
|
||||
The fixture explicitly disposes one definitely pre-LDAP synthetic reservation to
|
||||
move between fault cases; it does not clear an unknown native mutation. All
|
||||
source/fixture passes remain distinct from live CNPG/lease/TLS/backup continuity,
|
||||
protected owner callbacks, actual authoritative tenant scope, native LDAP fencing
|
||||
and production acceptance.
|
||||
|
||||
|
||||
## R2 cached helper selection
|
||||
|
||||
The reviewed R1 preflight failed before `execute`, session allocation or private
|
||||
import because its pinned helper image 1e6547 was absent. No pull or override was
|
||||
used. Frozen manifest/driver/probe/source and the coordinator's structured failure
|
||||
are retained under `revisions/r1`. There is no raw stderr file; the original
|
||||
traceback was returned in tool output only, so no byte-exact stderr artifact is
|
||||
invented.
|
||||
|
||||
The proposed R2 helper is the coordinator-observed cached f239 image, bound by
|
||||
existing published receipts to User Engine source 1bb7cacc. That source uses
|
||||
system pip on official Python 3.12, not a virtualenv. R2 therefore binds every
|
||||
helper launch/exec to `/usr/local/bin/python3`; it has no PATH or alternate-venv
|
||||
fallback. A closed in-container probe must verify the exact executable and import
|
||||
psycopg 3 before candidate source import, reporting the bundled version. Missing
|
||||
or mismatched interpreter/dependency refuses and preserves exact cleanup duties.
|
||||
No container count, limit, privilege, network or cleanup expansion is proposed.
|
||||
Historical producer or rollout acceptance is not a new image/dependency grant.
|
||||
See `helper-selection-r2.json`, `manifest-delta-r2.json` and `preparation-r2.json`.
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Default is a read-only plan. Docker execution awaits independent exact review."""
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import secrets
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
MANIFEST = HERE / 'manifest.json'
|
||||
|
||||
|
||||
def require(condition):
|
||||
if not condition:
|
||||
raise RuntimeError('bounded_synthetic_fixture_refused')
|
||||
|
||||
|
||||
def reviewed_manifest(expected):
|
||||
raw = MANIFEST.read_bytes()
|
||||
require(expected == hashlib.sha256(raw).hexdigest())
|
||||
manifest = json.loads(raw)
|
||||
for path, digest in manifest['source_sha256'].items():
|
||||
require(bool(re.fullmatch(r'sha256:[0-9a-f]{64}', digest)))
|
||||
require('sha256:' + hashlib.sha256(Path(path).read_bytes()).hexdigest() == digest)
|
||||
return manifest
|
||||
|
||||
|
||||
def load_containment(manifest):
|
||||
spec = importlib.util.spec_from_file_location('reviewed_existing_containment', manifest['containment_helper'])
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def session_plan(manifest, run_id):
|
||||
require(bool(re.fullmatch(r'[a-f0-9]{32}', run_id)))
|
||||
anchor = 'nk49-kc-' + run_id + '-journal-postgres'
|
||||
probe = 'nk49-kc-' + run_id + '-journal-probe'
|
||||
return {'run_id': run_id, 'containers': [anchor, probe], 'worker': anchor, 'issuer': probe,
|
||||
'images': {anchor: manifest['postgres_image'], probe: manifest['python_image']}, 'volumes': {}}
|
||||
|
||||
|
||||
def base_args(session, name, network, user="1000:1000"):
|
||||
uid, gid = user.split(":")
|
||||
return ['docker', 'run', '--detach', '--pull=never', '--name', name, '--network', network,
|
||||
'--label', 'net-kingdom.recovery-run=' + session['run_id'], '--read-only', '--cap-drop', 'ALL',
|
||||
'--security-opt', 'no-new-privileges', '--user', user, '--memory', '384m', '--cpus', '0.5',
|
||||
'--pids-limit', '64', '--log-driver', 'none', '--restart', 'no',
|
||||
'--tmpfs', '/tmp:rw,nosuid,nodev,noexec,size=16777216,uid=' + uid + ',gid=' + gid + ',mode=0700']
|
||||
|
||||
|
||||
def tar_bytes(files):
|
||||
out = io.BytesIO()
|
||||
with tarfile.open(fileobj=out, mode='w') as archive:
|
||||
for name, value in files.items():
|
||||
require(bool(re.fullmatch(r'[a-zA-Z0-9._/-]+', name)) and not name.startswith('/') and '..' not in Path(name).parts)
|
||||
entry = tarfile.TarInfo(name)
|
||||
entry.size = len(value)
|
||||
entry.mode = 0o600
|
||||
entry.uid = entry.gid = 1000
|
||||
archive.addfile(entry, io.BytesIO(value))
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
IMPORT = r'''
|
||||
import base64,json,os,sys
|
||||
from pathlib import Path
|
||||
os.umask(0o077)
|
||||
root=Path('/fixture')
|
||||
(root/'source').mkdir(mode=0o700)
|
||||
(root/'source/migrations').mkdir(mode=0o700)
|
||||
(root/'source/tests').mkdir(mode=0o700)
|
||||
for name,encoded in json.load(sys.stdin).items():
|
||||
if name not in ('source/provisioner.py','source/group_provenance.py','source/group_provenance_postgres.py','source/migrations/group_journal_postgres_v1.sql','source/tests/test_service_groups.py','probe.py','private.json'):raise ValueError()
|
||||
fd=os.open(root/name,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
with os.fdopen(fd,'wb') as output:output.write(base64.b64decode(encoded,validate=True))
|
||||
'''
|
||||
|
||||
|
||||
def helper_python(manifest):
|
||||
# Exact producer source installs into official Python's system prefix.
|
||||
# Never fall back to PATH or another interpreter/virtual environment.
|
||||
require(manifest.get('helper_interpreter') == '/usr/local/bin/python3')
|
||||
return manifest['helper_interpreter']
|
||||
|
||||
|
||||
INTERPRETER_PROBE = r"""
|
||||
import json,sys
|
||||
try:
|
||||
import psycopg
|
||||
valid=sys.executable=='/usr/local/bin/python3' and psycopg.__version__.split('.')[0]=='3'
|
||||
print(json.dumps({'helper_ready':valid,'executable':sys.executable,'psycopg_version':psycopg.__version__}))
|
||||
except Exception:
|
||||
print(json.dumps({'helper_ready':False}))
|
||||
"""
|
||||
|
||||
|
||||
def preflight(adapter, manifest):
|
||||
helper_python(manifest)
|
||||
adapter.docker_environment() # Refuse ambient alternate routing before calls.
|
||||
contexts = json.loads(adapter.command(['docker', 'context', 'inspect'], timeout=15))
|
||||
require(len(contexts) == 1 and contexts[0]['Endpoints']['docker']['Host'] == manifest['fixed_endpoint'])
|
||||
facts = json.loads(adapter.command(['docker', 'info', '--format',
|
||||
'{"ID":{{json .ID}},"Name":{{json .Name}},"OSType":{{json .OSType}}}'], timeout=15))
|
||||
require(facts == {'ID': manifest['engine_id'], 'Name': manifest['engine_name'], 'OSType': 'linux'})
|
||||
for image in (manifest['postgres_image'], manifest['python_image']):
|
||||
value = adapter.command(['docker', 'image', 'inspect', '--format', '{{.Id}}', image], timeout=15).decode().strip()
|
||||
require(bool(re.fullmatch(r'sha256:[a-f0-9]{64}', value)))
|
||||
|
||||
|
||||
def execute(manifest, adapter, result):
|
||||
session = session_plan(manifest, uuid.uuid4().hex)
|
||||
result['session'] = {'run_id': session['run_id'], 'containers': session['containers'],
|
||||
'label': 'net-kingdom.recovery-run=' + session['run_id']}
|
||||
python = helper_python(manifest)
|
||||
deadline = time.monotonic() + 210
|
||||
original_command = adapter.command
|
||||
def run(argv, **kwargs):
|
||||
remaining = deadline - time.monotonic()
|
||||
require(remaining > 0)
|
||||
try:
|
||||
return original_command(argv, timeout=min(50, remaining), **kwargs)
|
||||
except BaseException:
|
||||
result['daemon_request_uncertain'] = True
|
||||
raise
|
||||
adapter.command = run
|
||||
try:
|
||||
anchor = session['worker']
|
||||
args = base_args(session, anchor, 'none', user='999:999') + [
|
||||
'--tmpfs', '/var/lib/postgresql:rw,nosuid,nodev,noexec,size=1048576,uid=999,gid=999,mode=0700',
|
||||
'--tmpfs', '/var/lib/postgresql/data:rw,nosuid,nodev,noexec,size=134217728,uid=999,gid=999,mode=0700',
|
||||
'--tmpfs', '/run/fixture:rw,nosuid,nodev,noexec,size=1048576,uid=999,gid=999,mode=0700',
|
||||
'--env', 'PGDATA=/var/lib/postgresql/data', '--entrypoint', '/bin/sh',
|
||||
manifest['postgres_image'], '-c', 'sleep 240']
|
||||
run(args)
|
||||
adapter.inspect_session({**session, 'containers': [anchor], 'issuer': anchor}, worker_only=True)
|
||||
admin_password, runtime_password = secrets.token_hex(24), secrets.token_hex(24)
|
||||
run(['docker', 'exec', '-i', anchor, 'tar', '-xf', '-', '-C', '/run/fixture'],
|
||||
input=tar_bytes({'password': admin_password.encode()}))
|
||||
run(['docker', 'exec', '--detach', anchor, '/bin/sh', '-c',
|
||||
'initdb -D /var/lib/postgresql/data --username=fixture_admin --auth-local=trust --auth-host=scram-sha-256 --pwfile=/run/fixture/password > /dev/null 2>&1 && exec postgres -D /var/lib/postgresql/data -c listen_addresses=127.0.0.1 -c unix_socket_directories=/run/fixture > /dev/null 2>&1'])
|
||||
anchor_id = run(['docker', 'container', 'inspect', anchor, '--format', '{{.Id}}']).decode().strip()
|
||||
require(bool(re.fullmatch(r'[a-f0-9]{64}', anchor_id)))
|
||||
args = base_args(session, session['issuer'], 'container:' + anchor_id) + [
|
||||
'--tmpfs', '/fixture:rw,nosuid,nodev,noexec,size=16777216,uid=1000,gid=1000,mode=0700',
|
||||
'--entrypoint', python, manifest['python_image'], '-c', 'import time;time.sleep(240)']
|
||||
run(args)
|
||||
adapter.inspect_session(session, worker_only=True)
|
||||
result['containment_verified_before_generated_private_import'] = True
|
||||
interpreter = json.loads(run(['docker', 'exec', session['issuer'], python, '-c', INTERPRETER_PROBE], max_output=4096))
|
||||
require(interpreter.get('helper_ready') is True and interpreter.get('executable') == python)
|
||||
require(isinstance(interpreter.get('psycopg_version'), str) and interpreter['psycopg_version'].split('.')[0] == '3')
|
||||
result['helper_interpreter_verified_before_candidate_import'] = True
|
||||
result['helper_psycopg_version'] = interpreter['psycopg_version']
|
||||
files = {'source/' + name: (HERE.parent / 'postimage/identity-provisioner' / name).read_bytes()
|
||||
for name in ('provisioner.py', 'group_provenance.py', 'group_provenance_postgres.py',
|
||||
'migrations/group_journal_postgres_v1.sql')}
|
||||
files['source/tests/test_service_groups.py'] = (HERE / 'test_service_groups.py').read_bytes()
|
||||
files.update({'probe.py': (HERE / 'probe.py').read_bytes(), 'private.json': json.dumps({
|
||||
'admin_password': admin_password, 'runtime_password': runtime_password}).encode()})
|
||||
run(['docker', 'exec', '-i', session['issuer'], python, '-c', IMPORT],
|
||||
input=json.dumps({k: base64.b64encode(v).decode() for k, v in files.items()}).encode())
|
||||
result['checks'] = {}
|
||||
backup = None
|
||||
active_database = 'postgres'
|
||||
for phase in ('bootstrap', 'restart', 'concurrency', 'fork', 'lost-ack', 'restored'):
|
||||
if phase == 'restored':
|
||||
require(backup is not None)
|
||||
run(['docker', 'exec', '-i', anchor, 'pg_restore', '--host', '/run/fixture', '-U', 'fixture_admin',
|
||||
'--dbname', active_database, '--clean', '--if-exists', '--single-transaction', '--exit-on-error', '--no-owner'], input=backup)
|
||||
value = json.loads(run(['docker', 'exec', session['issuer'], python, '/fixture/probe.py', phase], max_output=8192))
|
||||
expected = set(manifest['phase_oracles'][phase])
|
||||
require(value.get('status') == 'passed' and set(value.get('checks', {})) == expected)
|
||||
require(all(v is True for v in value['checks'].values()))
|
||||
result['checks'].update(value['checks'])
|
||||
result['psycopg_version'] = value['psycopg_version']
|
||||
require(value.get('active_database') in {'postgres', 'fixture_fork'})
|
||||
active_database = value['active_database']
|
||||
if phase == 'bootstrap':
|
||||
backup = run(['docker', 'exec', anchor, 'pg_dump', '--host', '/run/fixture', '-U', 'fixture_admin',
|
||||
'--dbname', 'postgres', '--format=custom', '--no-owner'], max_output=4194304)
|
||||
result['status'] = 'synthetic_postgres_journal_compatibility_passed'
|
||||
finally:
|
||||
def cleanup_command(argv, **kwargs):
|
||||
try: return original_command(argv, timeout=15, **kwargs)
|
||||
except BaseException:
|
||||
result['daemon_request_uncertain'] = True
|
||||
raise
|
||||
adapter.command = cleanup_command
|
||||
try:
|
||||
adapter.cleanup(session, result)
|
||||
result['container_cleanup_verified'] = True
|
||||
except BaseException:
|
||||
result['status'] = 'failed'
|
||||
result['container_cleanup_verified'] = False
|
||||
adapter.command = original_command
|
||||
if result.get('daemon_request_uncertain'):
|
||||
result['status'] = 'failed'
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--plan', action='store_true')
|
||||
parser.add_argument('--execute-reviewed-fixture', action='store_true')
|
||||
parser.add_argument('--expected-manifest-sha256')
|
||||
args = parser.parse_args()
|
||||
if not args.execute_reviewed_fixture:
|
||||
print(MANIFEST.read_text())
|
||||
return 0
|
||||
require(not args.plan)
|
||||
manifest = reviewed_manifest(args.expected_manifest_sha256)
|
||||
adapter = load_containment(manifest)
|
||||
preflight(adapter, manifest)
|
||||
os.umask(0o077)
|
||||
output = Path(tempfile.mkdtemp(prefix='nk48-postgres-native-review-', dir='/tmp')) / 'result.json'
|
||||
result = {'schema': 'nk48.synthetic-postgres-journal-result.v1', 'status': 'failed',
|
||||
'manifest_sha256': args.expected_manifest_sha256, 'production_contacted': False,
|
||||
'actual_credentials_used': False, 'native_acceptance_claimed': False}
|
||||
try:
|
||||
execute(manifest, adapter, result)
|
||||
except BaseException:
|
||||
result['status'] = 'failed'
|
||||
finally:
|
||||
output.write_text(json.dumps(result, indent=2) + '\n')
|
||||
print(json.dumps({'status': result['status'], 'safe_result': str(output)}))
|
||||
return int(result['status'] != 'synthetic_postgres_journal_compatibility_passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"schema": "nk48.synthetic-postgres-journal-result.v1",
|
||||
"status": "failed",
|
||||
"manifest_sha256": "8ac8547b43ac1679acadd422993bc3fbce842a6c315a728af4d452734910240b",
|
||||
"production_contacted": false,
|
||||
"actual_credentials_used": false,
|
||||
"native_acceptance_claimed": false,
|
||||
"session": {
|
||||
"run_id": "e6e2c053a85346508fc0e35d4530b951",
|
||||
"containers": [
|
||||
"nk49-kc-e6e2c053a85346508fc0e35d4530b951-journal-postgres",
|
||||
"nk49-kc-e6e2c053a85346508fc0e35d4530b951-journal-probe"
|
||||
],
|
||||
"label": "net-kingdom.recovery-run=e6e2c053a85346508fc0e35d4530b951"
|
||||
},
|
||||
"containment_verified_before_generated_private_import": true,
|
||||
"helper_interpreter_verified_before_candidate_import": true,
|
||||
"helper_psycopg_version": "3.3.6",
|
||||
"checks": {},
|
||||
"container_cleanup_obligations": [
|
||||
{
|
||||
"container": "nk49-kc-e6e2c053a85346508fc0e35d4530b951-journal-probe",
|
||||
"status": "absence_verified"
|
||||
},
|
||||
{
|
||||
"container": "nk49-kc-e6e2c053a85346508fc0e35d4530b951-journal-postgres",
|
||||
"status": "absence_verified"
|
||||
}
|
||||
],
|
||||
"container_cleanup_verified": true
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"status": "fixture_helper_successor_proposed_not_executed",
|
||||
"selected_cached_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b",
|
||||
"producer_revision": "1bb7cacc98479f56f6b1c28c000479efd260a944",
|
||||
"producer_containerfile_sha256": "668a90308bb6307f95a8c5439e5d751f81bf1f7126755e26373f3523f07c858a",
|
||||
"producer_containerfile_public_source": "https://forgejo.coulomb.social/coulomb/user-engine/src/commit/1bb7cacc98479f56f6b1c28c000479efd260a944/Containerfile",
|
||||
"provenance_sources": [
|
||||
{
|
||||
"repo": "rapp-user-engine",
|
||||
"path": "docs/evidence/2026-10-11-published-readback-proposal/published-receipt.json",
|
||||
"revision": "a42c68f8701880bfeb93e28dd812fcf3691568a3",
|
||||
"sha256": "2f84959e36d442860fb2b5a5817bb707c4f8372797a3ef8dd0d5cab956af86ce",
|
||||
"public_source": "https://forgejo.coulomb.social/coulomb/rapp-user-engine/src/commit/a42c68f8701880bfeb93e28dd812fcf3691568a3/docs/evidence/2026-10-11-published-readback-proposal/published-receipt.json"
|
||||
},
|
||||
{
|
||||
"repo": "user-engine",
|
||||
"path": "docs/evidence/2026-10-11-one-app-catalogue-executor/isolated-postgres-receipt.json",
|
||||
"revision": "9557d6f867fe03c546aa26655f84b4192109981d",
|
||||
"sha256": "964cc3755e96330bf6d1ada63410f8adfdb3c45602e0446369d6dddace412405",
|
||||
"public_source": "https://forgejo.coulomb.social/coulomb/user-engine/src/commit/9557d6f867fe03c546aa26655f84b4192109981d/docs/evidence/2026-10-11-one-app-catalogue-executor/isolated-postgres-receipt.json"
|
||||
}
|
||||
],
|
||||
"selection_reason": "coordinator reports exact image cached; published source receipt binds image to producer; prior owner fixture used exact image for psycopg dependencies",
|
||||
"interpreter": "/usr/local/bin/python3",
|
||||
"interpreter_binding": "producer officialPython3.12+systempip runtime, no virtualenv; execution must verify exact sys.executable/psycopg3 before candidate import, no PATHfallback",
|
||||
"not_claimed": [
|
||||
"worker did not query Docker or create/run containers",
|
||||
"bundled psycopg version not observed yet; must be reported by exact helper",
|
||||
"historical image risk/production acceptance not transferred",
|
||||
"cached helper is not a future identity-provisioner image/dependency admission"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"from_manifest_sha256": "4a816f74f857dfbebb5eaa0de3d792c91cd5e0f52caf458c2f7789583507ee31",
|
||||
"to_manifest_sha256": "8ac8547b43ac1679acadd422993bc3fbce842a6c315a728af4d452734910240b",
|
||||
"source_patch_unchanged": "503e3d0217e6be25df8a58f3968cab7c00cdd9933d0fee2a66ef951fc4aae2b1",
|
||||
"helper_image": {
|
||||
"before": "forgejo.coulomb.social/coulomb/user-engine@sha256:1e6547c40c93c49f01438edcf76c20f561ccebc110220bae020723505aa56c70",
|
||||
"after": "forgejo.coulomb.social/coulomb/user-engine@sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b"
|
||||
},
|
||||
"helper_interpreter": {
|
||||
"before": "PATH python3",
|
||||
"after": "/usr/local/bin/python3",
|
||||
"verification": "closed exact sys.executable/psycopg3 probe before candidate source import"
|
||||
},
|
||||
"unchanged_contracts": {
|
||||
"postgres_image": true,
|
||||
"fixed_endpoint": true,
|
||||
"engine_id": true,
|
||||
"engine_name": true,
|
||||
"limits": true,
|
||||
"resources": true,
|
||||
"phase_oracles": true,
|
||||
"cleanup": true,
|
||||
"network": true,
|
||||
"pull_policy": true
|
||||
},
|
||||
"worker_docker_calls": false,
|
||||
"execution_requires_new_independent_review": true
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
{
|
||||
"schema": "nk48.postgres-journal-fixture-proposal.v1",
|
||||
"status": "prepared_not_executed_not_approved",
|
||||
"source_sha256": {
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py": "sha256:3dc033d7f1defc6c9b3d521889e0172aa05da3dadf4b9703d5c00a9beee9d9a9",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/probe.py": "sha256:97265a65f008e86c6f3de54eaa1a7d77f069117d4b923fb85cf835a8816adf00",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_driver.py": "sha256:98ca14d4919323effb6e9b32a88ba657ef3249c47b6ed85092b5c80eb5833457",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_service_groups.py": "sha256:815571fd92c0001dacbe250640acd99b2f2872b4171519ed54f8f469ee90aafe",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/source-manifest.json": "sha256:0c16e19415ad93b6ca73c92a05c205d263adde8a780c8c33119cd5f9d40f4752",
|
||||
"/home/worsch/net-kingdom/tools/bootstrap-recovery/isolated-keycape.py": "sha256:f67627b7d1647d75187dd899874efe22383bc91de5d97dabb9801076668ab3c2",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/provisioner.py": "sha256:19d1b837cbafeeacb1aa815856cd68091e6702281d72b1d860230fa320e1725b",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/group_provenance.py": "sha256:405e08eaaab7c21d618dff4db5970607806f32ab6c461effb81c737e32182b10",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/group_provenance_postgres.py": "sha256:55282af26f95af66ff8d444d38d0bcbcf3ee58a615adedfff8da5db2b0716889",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/migrations/group_journal_postgres_v1.sql": "sha256:3cdf82194d6f1e2dd865b0c7e7b8a14447fda8c49151148134a0d40a80fb46ac",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/README.md": "sha256:0dde6c2831b4a836824a6bec1d78f1014306bca27dcb33fa5d923ddd9cbe7cba",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/README.md": "sha256:4bf4678a1f1c70fbd3d4ffa77c70821be0372f03210b2164a2895fc99ca6a6b9",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/helper-selection-r2.json": "sha256:ec5464c0d5310028ab91073f985183af01da2d194358fad412d197dbf90106d9",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/producer-Containerfile.txt": "sha256:668a90308bb6307f95a8c5439e5d751f81bf1f7126755e26373f3523f07c858a"
|
||||
},
|
||||
"candidate_patch_sha256": "503e3d0217e6be25df8a58f3968cab7c00cdd9933d0fee2a66ef951fc4aae2b1",
|
||||
"containment_helper": "/home/worsch/net-kingdom/tools/bootstrap-recovery/isolated-keycape.py",
|
||||
"fixed_endpoint": "unix:///var/run/docker.sock",
|
||||
"engine_id": "74f93783-6cef-43a8-a3c4-f62586cd3bc1",
|
||||
"engine_name": "bnt-lap001",
|
||||
"postgres_image": "postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70",
|
||||
"python_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b",
|
||||
"image_pin_origin": "PostgreSQL pin unchanged; cached f239 helper selected from coordinator inventory and tied to UserEngine producer1bb7cacc by existing published receipt/previous owner PostgreSQL fixture; fixture-only selection, no future image admission",
|
||||
"pull_policy": "never",
|
||||
"network": "network-none",
|
||||
"scope": "real disposable PostgreSQL backend SQL/migration/concurrency/restore compatibility; LDAP is explicitly the existing synthetic test Directory, not actual native LDAP in this fixture",
|
||||
"limits": {
|
||||
"max_service_containers": 2,
|
||||
"host_ports": 0,
|
||||
"host_mounts": 0,
|
||||
"named_or_anonymous_volumes": 0,
|
||||
"memory_bytes_per_container": 402653184,
|
||||
"cpus_per_container": 0.5,
|
||||
"pids_per_container": 64,
|
||||
"postgres_tmpfs_bytes": 153092096,
|
||||
"helper_tmpfs_bytes": 33554432,
|
||||
"backup_in_host_memory_max_bytes": 4194304,
|
||||
"container_lifetime_seconds": 240,
|
||||
"execution_deadline_seconds": 210,
|
||||
"docker_rpc_timeout_seconds": 50,
|
||||
"preflight_rpc_timeout_seconds": 15,
|
||||
"cleanup_rpc_timeout_seconds": 15,
|
||||
"cleanup_total_bound_seconds": 120,
|
||||
"new_cloud_spend_eur": 0
|
||||
},
|
||||
"compatibility_preconditions": [
|
||||
"exact cached image/engine pins reverified by admitted preflight, never pulled",
|
||||
"PostgreSQL image supports nonroot UID999 with initdb/postgres/pg_dump/pg_restore in PATH; failed compatibility refuses/cleans",
|
||||
"User Engine f239 producer source uses official Python system prefix; exact /usr/local/bin/python3 and bundled psycopg3 must pass closed probe before candidate import, no PATH/venv fallback or downloads",
|
||||
"all image-declared volumes must be masked by private tmpfs; existing containment guard refuses any anonymous volume before private import"
|
||||
],
|
||||
"resources": {
|
||||
"container_names": "nk49-kc-<fresh32hex>-journal-{postgres,probe}",
|
||||
"exact_label": "net-kingdom.recovery-run=<same-fresh32hex>",
|
||||
"private_state": "generated admin/runtime passwords, source/migration, synthetic directory, external test checkpoint within private container tmpfs",
|
||||
"safe_result": "private /tmp/nk48-postgres-native-review-<suffix>/result.json"
|
||||
},
|
||||
"phase_oracles": {
|
||||
"bootstrap": [
|
||||
"only_loopback_no_external_routes",
|
||||
"actual_postgresql_journal_ensure",
|
||||
"explicit_scope_context_retained",
|
||||
"confirmed_inventory_excludes_preexisting",
|
||||
"runtime_ddl_denied"
|
||||
],
|
||||
"restart": [
|
||||
"only_loopback_no_external_routes",
|
||||
"separate_process_postgres_restart_and_revoke",
|
||||
"preexisting_shared_state_preserved",
|
||||
"foreign_context_receipt_refused"
|
||||
],
|
||||
"concurrency": [
|
||||
"only_loopback_no_external_routes",
|
||||
"concurrent_original_reservation_exactly_once",
|
||||
"stale_version_update_refused_without_head_advance"
|
||||
],
|
||||
"lost-ack": [
|
||||
"only_loopback_no_external_routes",
|
||||
"lost_checkpoint_ack_retains_pending_reservation",
|
||||
"reopened_uncertainty_refuses_ldap_effects",
|
||||
"active_fence_survives_process_reopen",
|
||||
"lost_synthetic_directory_response_retains_postgres_intent",
|
||||
"lost_sql_commit_reply_retains_committed_head",
|
||||
"reopened_stale_checkpoint_refuses_continuation"
|
||||
],
|
||||
"restored": [
|
||||
"only_loopback_no_external_routes",
|
||||
"actual_pg_restore_behind_checkpoint_detected",
|
||||
"restored_store_cannot_gain_replay_authority"
|
||||
],
|
||||
"fork": [
|
||||
"only_loopback_no_external_routes",
|
||||
"concurrent_divergent_clones_same_counter_verified",
|
||||
"independent_exact_transition_cas_selects_one_winner",
|
||||
"reopened_loser_clone_refused_at_same_counter"
|
||||
]
|
||||
},
|
||||
"restore": "actual generated pg_dump after bootstrap; winner database advances through reopen/concurrency/fork/uncertainty; actual pg_restore of original archive while independent content checkpoint survives; runtime SELECT must work and show older restored head before refusal can pass",
|
||||
"cleanup": "finally reuses exact name/label cleanup for both preregistered containers, including failed/lost-create replies; successful explicit absence required; any Docker uncertainty remains failed; no prune/global removal",
|
||||
"not_claimed": [
|
||||
"CNPG HA, native runtime storage/backup/restore admission",
|
||||
"authoritative tenant/case/PDP/evidence/session owner callbacks",
|
||||
"external LDAP writer fencing/CAS/ABA safety",
|
||||
"production endpoint activation, credentials or owner assent",
|
||||
"KeyCape hard-parent-loss reaper proof"
|
||||
],
|
||||
"forbidden": [
|
||||
"Docker commands before exact independent review",
|
||||
"pull/build/push, host networking/ports/mounts, production APIs",
|
||||
"real database/LDAP, identity archives, credentials from environment/custody",
|
||||
"clearing production restore quarantine or manufacturing scope/authority"
|
||||
],
|
||||
"execution_command": [
|
||||
"python3",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py",
|
||||
"--execute-reviewed-fixture",
|
||||
"--expected-manifest-sha256",
|
||||
"<reviewed-exact-manifest-digest>"
|
||||
],
|
||||
"fork": "actual PostgreSQL CREATE DATABASE TEMPLATE clone; both stores pass exact same old external marker check under test barrier, commit different bodies at same counter, race external strict CAS; exactly one wins and fresh loser store refuses; no directory effect in this direct reservation case",
|
||||
"revision": "r2-cached-helper-explicit-interpreter",
|
||||
"helper_interpreter": "/usr/local/bin/python3",
|
||||
"supersedes_manifest_sha256": "4a816f74f857dfbebb5eaa0de3d792c91cd5e0f52caf458c2f7789583507ee31",
|
||||
"r1_preflight": "failed missing helper before execute/session/private import; preserved structured failure, no raw stderr file exists"
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"status": "failed_exact_subphase_unknown",
|
||||
"source": "coordinator execution and safe receipt; no worker Docker calls",
|
||||
"manifest_sha256": "8ac8547b43ac1679acadd422993bc3fbce842a6c315a728af4d452734910240b",
|
||||
"receipt_sha256": "fb134a5f0444f7aabb6ea8f48f7e9bcdeb028bf42faecd477d0eeee882f14d3c",
|
||||
"containment_verified": true,
|
||||
"interpreter_verified": true,
|
||||
"bundled_psycopg_version": "3.3.6",
|
||||
"checks": {},
|
||||
"both_containers_absence_verified_in_receipt": true,
|
||||
"daemon_uncertainty_reported": false,
|
||||
"diagnostic_limit": "R2driver discarded failed helperresponse before retaining it; bootstrap plausible but not proved; no exact failure retroassigned"
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"status": "prepared_review_required_not_executed",
|
||||
"fixture_plan_command": "python3 -m pytest -q docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_driver.py",
|
||||
"fixture_plan_result": "9 passed in0.05s",
|
||||
"source_backend_unchanged": true,
|
||||
"source_patch_sha256": "503e3d0217e6be25df8a58f3968cab7c00cdd9933d0fee2a66ef951fc4aae2b1",
|
||||
"r2_manifest_sha256": "8ac8547b43ac1679acadd422993bc3fbce842a6c315a728af4d452734910240b",
|
||||
"r1_manifest_sha256": "4a816f74f857dfbebb5eaa0de3d792c91cd5e0f52caf458c2f7789583507ee31",
|
||||
"worker_docker_executed": false,
|
||||
"root_reported_r1_preflight": "missing helper, failed before execute/session/private import; structured original retained, rawstderrfile absent",
|
||||
"native_success_claimed": false,
|
||||
"producer_history_is_not_future_image_admission": true
|
||||
}
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
"""Synthetic PostgreSQL journal compatibility; LDAP remains a declared test double."""
|
||||
import concurrent.futures
|
||||
from dataclasses import replace
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import uuid
|
||||
|
||||
ROOT = Path('/fixture')
|
||||
sys.path[:0] = [str(ROOT / 'source'), str(ROOT / 'source/tests')]
|
||||
import psycopg
|
||||
from group_provenance import GroupProvenanceUnavailable
|
||||
from group_provenance_postgres import GroupJournalBinding, PostgresGroupProvenance, psycopg_connector, journal_genesis
|
||||
from test_service_groups import Directory, req
|
||||
|
||||
EPOCH = 'bfbb8939-e88c-4f9c-a5c6-14ba3e399ddf'
|
||||
CONTEXT = GroupJournalBinding('tenant:synthetic:one', 'synthetic-pg-directory', 'sha256:' + '1' * 64,
|
||||
'sha256:' + '2' * 64, 'synthetic-principal', EPOCH)
|
||||
|
||||
|
||||
def require(value):
|
||||
if not value:
|
||||
raise RuntimeError('postgres_fixture_oracle_failed')
|
||||
|
||||
|
||||
def info(admin=False, database=None):
|
||||
values = json.loads((ROOT / 'private.json').read_text())
|
||||
database = database or ((ROOT / 'active-database').read_text() if (ROOT / 'active-database').exists() else 'postgres')
|
||||
require(database in {'postgres', 'fixture_fork'})
|
||||
return {'host': '127.0.0.1', 'port': 5432, 'dbname': database, 'user': 'fixture_admin' if admin else 'fixture_app',
|
||||
'password': values['admin_password'] if admin else values['runtime_password'], 'sslmode': 'disable'}
|
||||
|
||||
|
||||
class FileCheckpoint:
|
||||
"""Fixture-only independently retained checkpoint; never a native owner grant."""
|
||||
def __init__(self):
|
||||
self.path = ROOT / 'checkpoint.json'
|
||||
def _apply(self, epoch, directory, previous, previous_digest, current=None, current_digest=None):
|
||||
with (ROOT / 'checkpoint.lock').open('a+') as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX)
|
||||
value = json.loads(self.path.read_text())
|
||||
require(value == {'epoch': epoch, 'directory': directory, 'head': previous, 'digest': previous_digest})
|
||||
if current is not None:
|
||||
temporary = ROOT / ('checkpoint-' + uuid.uuid4().hex)
|
||||
with temporary.open('x') as out:
|
||||
json.dump({'epoch': epoch, 'directory': directory, 'head': current, 'digest': current_digest}, out)
|
||||
out.flush(); os.fsync(out.fileno())
|
||||
os.replace(temporary, self.path)
|
||||
fd = os.open(ROOT, os.O_RDONLY | os.O_DIRECTORY)
|
||||
try: os.fsync(fd)
|
||||
finally: os.close(fd)
|
||||
return True
|
||||
def check(self, epoch, directory, head, digest):
|
||||
return self._apply(epoch, directory, head, digest)
|
||||
def advance(self, epoch, directory, previous, previous_digest, current, current_digest):
|
||||
return self._apply(epoch, directory, previous, previous_digest, current, current_digest)
|
||||
|
||||
|
||||
def store(context=CONTEXT, checkpoint=None, database=None):
|
||||
return PostgresGroupProvenance(connect=psycopg_connector(connection_info=lambda: info(database=database)), binding=context,
|
||||
continuity=checkpoint or FileCheckpoint())
|
||||
|
||||
|
||||
def refused(call):
|
||||
try: call()
|
||||
except (ValueError, RuntimeError): return
|
||||
raise RuntimeError('postgres_fixture_expected_refusal_missing')
|
||||
|
||||
|
||||
def snapshot(directory):
|
||||
return {'users': sorted(directory.users), 'groups': directory.groups,
|
||||
'members': {str(k): sorted(v) for k, v in directory.members.items()}}
|
||||
|
||||
|
||||
def restore_directory(value):
|
||||
directory = Directory()
|
||||
directory.users = set(value['users'])
|
||||
directory.groups = value['groups']
|
||||
directory.members = {int(k): set(v) for k, v in value['members'].items()}
|
||||
directory.group_provenance = store()
|
||||
return directory
|
||||
|
||||
|
||||
def bootstrap():
|
||||
for _ in range(80):
|
||||
try:
|
||||
db = psycopg.connect(**info(admin=True), connect_timeout=1)
|
||||
db.close(); break
|
||||
except Exception: time.sleep(.25)
|
||||
else: raise RuntimeError('synthetic_postgres_not_ready')
|
||||
from psycopg import sql
|
||||
with psycopg.connect(**info(admin=True)) as db:
|
||||
db.execute('CREATE SCHEMA identity_provisioner')
|
||||
db.execute((ROOT / 'source/migrations/group_journal_postgres_v1.sql').read_text())
|
||||
db.execute('INSERT INTO identity_provisioner.group_journal_meta VALUES (true, %s, %s, 0, %s, false)', (EPOCH, CONTEXT.directory, journal_genesis(EPOCH, CONTEXT.directory)))
|
||||
db.execute(sql.SQL('CREATE ROLE fixture_app LOGIN PASSWORD {}').format(sql.Literal(info()['password'])))
|
||||
db.execute('GRANT USAGE ON SCHEMA identity_provisioner TO fixture_app')
|
||||
db.execute('GRANT SELECT, INSERT, UPDATE ON identity_provisioner.group_receipts TO fixture_app')
|
||||
db.execute('GRANT SELECT ON identity_provisioner.group_journal_meta TO fixture_app')
|
||||
db.execute('GRANT UPDATE (head,head_digest) ON identity_provisioner.group_journal_meta TO fixture_app')
|
||||
(ROOT / 'checkpoint.json').write_text(json.dumps({'epoch': EPOCH, 'directory': CONTEXT.directory, 'head': 0, 'digest': journal_genesis(EPOCH, CONTEXT.directory)}))
|
||||
directory = Directory()
|
||||
directory.members[3].add('tegwick')
|
||||
before = snapshot(directory)
|
||||
directory.group_provenance = store()
|
||||
result = directory.ensure_service_groups(req())
|
||||
require(result.status == 'groups_ensured')
|
||||
require({x['name'] for x in result.created_groups} == {'forge-coulomb-owners', 'forge-coulomb-readers'})
|
||||
require({(x['name'], x['username']) for x in result.created_memberships} == {('forge-coulomb-owners', 'tegwick')})
|
||||
(ROOT / 'directory.json').write_text(json.dumps({'before': before, 'after': snapshot(directory)}))
|
||||
with psycopg.connect(**info()) as db:
|
||||
try: db.execute('CREATE TABLE identity_provisioner.runtime_ddl_must_fail (id integer)')
|
||||
except psycopg.errors.InsufficientPrivilege: pass
|
||||
else: raise RuntimeError('runtime_ddl_not_denied')
|
||||
return {'actual_postgresql_journal_ensure': True, 'explicit_scope_context_retained': True,
|
||||
'confirmed_inventory_excludes_preexisting': True, 'runtime_ddl_denied': True}
|
||||
|
||||
|
||||
def restart():
|
||||
saved = json.loads((ROOT / 'directory.json').read_text())
|
||||
directory = restore_directory(saved['after'])
|
||||
require(directory.ensure_service_groups(req()).resumed)
|
||||
require(not directory.calls)
|
||||
result = directory.revoke_service_groups(req(ensure_idempotency_key=req()['idempotency_key'],
|
||||
ensure_correlation_id=req()['correlation_id'], delete_if_empty=True))
|
||||
require(result.status == 'groups_revoked_and_deleted')
|
||||
require(snapshot(directory) == saved['before'])
|
||||
foreign = store(replace(CONTEXT, principal='other-synthetic-principal'))
|
||||
refused(lambda: foreign.get(req()['idempotency_key'], directory._group_binding(req()['groups'], req()['members'], req()['correlation_id'])))
|
||||
return {'separate_process_postgres_restart_and_revoke': True, 'preexisting_shared_state_preserved': True,
|
||||
'foreign_context_receipt_refused': True}
|
||||
|
||||
|
||||
def concurrency():
|
||||
binding = '4' * 64
|
||||
def reserve(_):
|
||||
try: return store().reserve('synthetic-concurrent-reservation', binding)
|
||||
except GroupProvenanceUnavailable: return None
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as pool:
|
||||
results = list(pool.map(reserve, range(24)))
|
||||
created = [r[0] for r in results if r is not None and r[1]]
|
||||
require(len(created) == 1)
|
||||
receipt = created[0]
|
||||
receipt['state'] = 'refused'
|
||||
store().save(receipt)
|
||||
stale = json.loads(json.dumps(receipt))
|
||||
store().save(receipt)
|
||||
head = json.loads((ROOT / 'checkpoint.json').read_text())['head']
|
||||
refused(lambda: store().save(stale))
|
||||
require(json.loads((ROOT / 'checkpoint.json').read_text())['head'] == head)
|
||||
with psycopg.connect(**info()) as db:
|
||||
require(db.execute('SELECT count(*) FROM identity_provisioner.group_receipts WHERE key=%s', ('synthetic-concurrent-reservation',)).fetchone()[0] == 1)
|
||||
return {'concurrent_original_reservation_exactly_once': True, 'stale_version_update_refused_without_head_advance': True}
|
||||
|
||||
|
||||
def fork():
|
||||
# Clone actual PostgreSQL state at the exact same head/digest. Both callers
|
||||
# must pass the old external check before either commits a divergent body.
|
||||
with psycopg.connect(**{**info(admin=True), 'dbname': 'template1'}, autocommit=True) as db:
|
||||
db.execute('CREATE DATABASE fixture_fork TEMPLATE postgres')
|
||||
barrier = threading.Barrier(2)
|
||||
class SimultaneousCheckpoint(FileCheckpoint):
|
||||
def check(self, *args):
|
||||
result = super().check(*args)
|
||||
barrier.wait(timeout=10)
|
||||
return result
|
||||
def attempt(database):
|
||||
candidate = store(database=database, checkpoint=SimultaneousCheckpoint())
|
||||
try:
|
||||
record, fresh = candidate.reserve('synthetic-fork-' + database, '8' * 64)
|
||||
require(fresh)
|
||||
return database, record
|
||||
except GroupProvenanceUnavailable:
|
||||
return database, None
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as pool:
|
||||
results = list(pool.map(attempt, ('postgres', 'fixture_fork')))
|
||||
winners = [(database, record) for database, record in results if record is not None]
|
||||
require(len(winners) == 1)
|
||||
winner, record = winners[0]
|
||||
loser = next(database for database, result in results if result is None)
|
||||
markers = {}
|
||||
for database in ('postgres', 'fixture_fork'):
|
||||
with psycopg.connect(**info(admin=True, database=database)) as db:
|
||||
markers[database] = db.execute('SELECT head,head_digest FROM identity_provisioner.group_journal_meta').fetchone()
|
||||
require(markers[winner][0] == markers[loser][0] and markers[winner][1] != markers[loser][1])
|
||||
store(database=winner).check()
|
||||
refused(lambda: store(database=loser).check())
|
||||
retained = json.loads((ROOT / 'checkpoint.json').read_text())
|
||||
require((retained['head'], retained['digest']) == markers[winner])
|
||||
# No directory call was attempted by this direct reservation fixture. Retain
|
||||
# terminal refusal for that key before continuing against the winner only.
|
||||
record['state'] = 'refused'
|
||||
store(database=winner).save(record)
|
||||
(ROOT / 'active-database').write_text(winner)
|
||||
return {'concurrent_divergent_clones_same_counter_verified': True,
|
||||
'independent_exact_transition_cas_selects_one_winner': True,
|
||||
'reopened_loser_clone_refused_at_same_counter': True}
|
||||
|
||||
|
||||
def restored():
|
||||
# Actual pg_restore has rolled SQL back while this checkpoint survived.
|
||||
with psycopg.connect(**info()) as db:
|
||||
epoch, directory, head, quarantine = db.execute('SELECT epoch::text,directory,head,quarantined FROM identity_provisioner.group_journal_meta').fetchone()
|
||||
require(epoch == EPOCH and directory == CONTEXT.directory and quarantine is False)
|
||||
require(head < json.loads((ROOT / 'checkpoint.json').read_text())['head'])
|
||||
refused(lambda: store().check())
|
||||
refused(lambda: store().reserve('no-replay-after-restore', '6' * 64))
|
||||
return {'actual_pg_restore_behind_checkpoint_detected': True, 'restored_store_cannot_gain_replay_authority': True}
|
||||
|
||||
|
||||
def lost_ack():
|
||||
class LoseAcknowledgment(FileCheckpoint):
|
||||
def advance(self, *args):
|
||||
super().advance(*args)
|
||||
raise RuntimeError('synthetic-lost-ack-after-durable-checkpoint')
|
||||
directory = Directory()
|
||||
directory.group_provenance = store(checkpoint=LoseAcknowledgment())
|
||||
refused(lambda: directory.ensure_service_groups(req(idempotency_key='synthetic-lost-sql-ack-key')))
|
||||
require(not directory.calls)
|
||||
directory.group_provenance = store()
|
||||
refused(lambda: directory.ensure_service_groups(req(idempotency_key='synthetic-lost-sql-ack-key')))
|
||||
require(not directory.calls)
|
||||
receipt = store().get('synthetic-lost-sql-ack-key', directory._group_binding(req()['groups'], req()['members'], req()['correlation_id']))
|
||||
require(receipt['state'] == 'pending')
|
||||
refused(lambda: store().reserve('unrelated-after-unresolved', '7' * 64))
|
||||
# Definite no-directory-call refusal is disposed only inside this generated
|
||||
# fixture, before moving to the next synthetic fault. No native recovery API.
|
||||
receipt['state'] = 'refused'
|
||||
store().save(receipt)
|
||||
directory = Directory()
|
||||
directory.group_provenance = store()
|
||||
original = directory._gql
|
||||
def lose_directory_response(token, query, variables):
|
||||
result = original(token, query, variables)
|
||||
if 'addUserToGroup' in query:
|
||||
raise RuntimeError('synthetic-directory-response-lost')
|
||||
return result
|
||||
directory._gql = lose_directory_response
|
||||
payload = req(idempotency_key='synthetic-lost-directory-response')
|
||||
refused(lambda: directory.ensure_service_groups(payload))
|
||||
checked = snapshot(directory)
|
||||
directory.group_provenance = store()
|
||||
refused(lambda: directory.ensure_service_groups(payload))
|
||||
require(snapshot(directory) == checked)
|
||||
record = store().get(payload['idempotency_key'], directory._group_binding(payload['groups'], payload['members'], payload['correlation_id']))
|
||||
require(record['state'] == 'uncertain' and record['intent']['operation'] == 'add_member')
|
||||
class LostCommitReply:
|
||||
def __init__(self): self.db = psycopg.connect(**info())
|
||||
def __enter__(self): self.db.__enter__(); return self
|
||||
def __exit__(self, typ, value, traceback):
|
||||
result = self.db.__exit__(typ, value, traceback)
|
||||
if typ is None: raise OSError('synthetic-reply-lost-after-actual-sql-commit')
|
||||
return result
|
||||
def cursor(self): return self.db.cursor()
|
||||
def close(self): self.db.close()
|
||||
broken = PostgresGroupProvenance(connect=LostCommitReply, binding=CONTEXT, continuity=FileCheckpoint())
|
||||
checkpoint = json.loads((ROOT / 'checkpoint.json').read_text())['head']
|
||||
refused(lambda: broken.save(record))
|
||||
with psycopg.connect(**info()) as db:
|
||||
committed_head = db.execute('SELECT head FROM identity_provisioner.group_journal_meta').fetchone()[0]
|
||||
require(committed_head == checkpoint + 1)
|
||||
require(json.loads((ROOT / 'checkpoint.json').read_text())['head'] == checkpoint)
|
||||
refused(lambda: store().check())
|
||||
return {'lost_checkpoint_ack_retains_pending_reservation': True, 'reopened_uncertainty_refuses_ldap_effects': True,
|
||||
'active_fence_survives_process_reopen': True, 'lost_synthetic_directory_response_retains_postgres_intent': True,
|
||||
'lost_sql_commit_reply_retains_committed_head': True, 'reopened_stale_checkpoint_refuses_continuation': True}
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
require({p.name for p in Path('/sys/class/net').iterdir()} == {'lo'})
|
||||
require(len(Path('/proc/net/route').read_text().splitlines()) == 1)
|
||||
checks = {'only_loopback_no_external_routes': True}
|
||||
checks.update({'bootstrap': bootstrap, 'restart': restart, 'concurrency': concurrency,
|
||||
'fork': fork, 'restored': restored, 'lost-ack': lost_ack}[sys.argv[1]]())
|
||||
print(json.dumps({'status': 'passed', 'checks': checks, 'psycopg_version': psycopg.__version__, 'active_database': info()['dbname']}))
|
||||
except Exception:
|
||||
print(json.dumps({'status': 'failed', 'failure': 'synthetic_pg_oracle_failed', 'phase': sys.argv[1]}))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1
|
||||
|
||||
RUN useradd --create-home --uid 10001 user-engine
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml README.md /app/
|
||||
COPY src /app/src
|
||||
RUN pip install --no-cache-dir ".[runtime]"
|
||||
|
||||
USER 10001:10001
|
||||
EXPOSE 8080
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
CMD ["python", "-m", "user_engine.runtime"]
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import contextlib
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tarfile
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
spec = importlib.util.spec_from_file_location('group_fixture_driver', HERE / 'driver.py')
|
||||
f = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(f)
|
||||
|
||||
|
||||
class PlanTests(unittest.TestCase):
|
||||
def test_default_and_explicit_plan_do_not_load_containment_or_generate_material(self):
|
||||
for args in ([], ['--plan']):
|
||||
with patch.object(sys, 'argv', ['fixture', *args]), patch.object(f, 'load_containment') as load, patch.object(f, 'execute') as execute, patch.object(f.secrets, 'token_hex') as generate, contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
self.assertEqual(0, f.main())
|
||||
self.assertEqual('prepared_not_executed_not_approved', json.loads(out.getvalue())['status'])
|
||||
load.assert_not_called(); execute.assert_not_called(); generate.assert_not_called()
|
||||
|
||||
def test_wrong_manifest_digest_refuses_before_loading_helper(self):
|
||||
with patch.object(sys, 'argv', ['fixture', '--execute-reviewed-fixture', '--expected-manifest-sha256', '0' * 64]), patch.object(f, 'load_containment') as load:
|
||||
with self.assertRaises(RuntimeError):
|
||||
f.main()
|
||||
load.assert_not_called()
|
||||
|
||||
def test_manifest_exact_source_closure_and_images(self):
|
||||
manifest = f.reviewed_manifest(hashlib.sha256(f.MANIFEST.read_bytes()).hexdigest())
|
||||
self.assertEqual('unix:///var/run/docker.sock', manifest['fixed_endpoint'])
|
||||
self.assertEqual('never', manifest['pull_policy'])
|
||||
self.assertEqual('network-none', manifest['network'])
|
||||
self.assertEqual(0, manifest['limits']['host_ports'])
|
||||
self.assertEqual(0, manifest['limits']['host_mounts'])
|
||||
self.assertEqual(2, manifest['limits']['max_service_containers'])
|
||||
self.assertIn('sha256:', manifest['postgres_image'])
|
||||
self.assertIn('sha256:', manifest['python_image'])
|
||||
|
||||
def test_exact_fresh_names_no_host_network_ports_or_mounts(self):
|
||||
manifest = json.loads(f.MANIFEST.read_text())
|
||||
session = f.session_plan(manifest, 'f' * 32)
|
||||
self.assertEqual(2, len(set(session['containers'])))
|
||||
for name in session['containers']:
|
||||
args = f.base_args(session, name, 'none')
|
||||
self.assertIn('--pull=never', args)
|
||||
self.assertIn('--read-only', args)
|
||||
self.assertIn('--log-driver', args)
|
||||
self.assertIn('net-kingdom.recovery-run=' + 'f' * 32, args)
|
||||
self.assertFalse({'--publish', '-p', '--volume', '-v', '--privileged', '--network=host'} & set(args))
|
||||
with self.assertRaises(RuntimeError):
|
||||
f.session_plan(manifest, 'not-a-fresh-run')
|
||||
|
||||
def test_tar_private_only_generated_named_material(self):
|
||||
raw = f.tar_bytes({'password': b'synthetic-only', 'lldap.toml': b'synthetic-config'})
|
||||
with tarfile.open(fileobj=io.BytesIO(raw)) as archive:
|
||||
self.assertTrue(all(member.uid == member.gid == 1000 and member.mode == 0o600 for member in archive))
|
||||
for path in ('../outside', '/absolute', 'path/../../escape'):
|
||||
with self.assertRaises(RuntimeError):
|
||||
f.tar_bytes({path: b'synthetic'})
|
||||
|
||||
def test_failure_still_invokes_existing_exact_cleanup(self):
|
||||
manifest = json.loads(f.MANIFEST.read_text())
|
||||
adapter = Mock()
|
||||
adapter.command.side_effect = RuntimeError('synthetic transport fault')
|
||||
result = {'status': 'failed'}
|
||||
with self.assertRaises(RuntimeError):
|
||||
f.execute(manifest, adapter, result)
|
||||
adapter.cleanup.assert_called_once()
|
||||
session = adapter.cleanup.call_args[0][0]
|
||||
self.assertEqual(session['containers'], result['session']['containers'])
|
||||
self.assertTrue(result['daemon_request_uncertain'])
|
||||
self.assertEqual('failed', result['status'])
|
||||
|
||||
def test_every_declared_source_input_exists_and_schema_does_not_admit_epoch(self):
|
||||
source = HERE.parent / 'postimage/identity-provisioner'
|
||||
for name in ('provisioner.py', 'group_provenance.py', 'group_provenance_postgres.py', 'migrations/group_journal_postgres_v1.sql'):
|
||||
self.assertTrue((source / name).is_file())
|
||||
self.assertTrue((HERE / 'test_service_groups.py').is_file())
|
||||
schema = (source / 'migrations/group_journal_postgres_v1.sql').read_text()
|
||||
self.assertNotIn('INSERT INTO', schema)
|
||||
self.assertIn('DEFAULT true', schema)
|
||||
self.assertIn('active boolean UNIQUE', schema)
|
||||
|
||||
def test_exact_producer_interpreter_has_no_path_or_venv_fallback(self):
|
||||
manifest = json.loads(f.MANIFEST.read_text())
|
||||
self.assertEqual('/usr/local/bin/python3', f.helper_python(manifest))
|
||||
for value in ('python3', '/opt/venv/bin/python', '/usr/bin/python3', None):
|
||||
with self.assertRaises(RuntimeError):
|
||||
f.helper_python({**manifest, 'helper_interpreter': value})
|
||||
compile(f.INTERPRETER_PROBE, 'public-interpreter-probe', 'exec')
|
||||
source = (HERE / 'driver.py').read_text()
|
||||
self.assertIn("'helper_interpreter_verified_before_candidate_import'", source)
|
||||
self.assertNotIn("'--entrypoint', 'python3'", source)
|
||||
|
||||
def test_probe_syntax_and_no_network_expansion(self):
|
||||
compile((HERE / 'probe.py').read_text(), str(HERE / 'probe.py'), 'exec')
|
||||
compile(f.IMPORT, 'private-import', 'exec')
|
||||
self.assertNotIn('subprocess', (HERE / 'probe.py').read_text())
|
||||
self.assertIn("'host': '127.0.0.1'", (HERE / 'probe.py').read_text())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
import unittest
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from group_provenance import GroupProvenance
|
||||
|
||||
from provisioner import LLDAPProvisioner, dispatch
|
||||
|
||||
|
||||
class Directory(LLDAPProvisioner):
|
||||
"""In-memory LLDAP: users, groups and memberships."""
|
||||
|
||||
def __init__(self):
|
||||
self.storage = tempfile.TemporaryDirectory()
|
||||
self.group_provenance = GroupProvenance(str(Path(self.storage.name) / "groups.db"), initialize=True)
|
||||
self.users = {"tegwick", "agent-codex"}
|
||||
self.groups = {"lldap_admin": 1, "tenant:trial:one:users": 2, "forge-admins": 3}
|
||||
self.members = {1: {"tegwick"}, 2: set(), 3: set()}
|
||||
self.calls = []
|
||||
self.drop_add = False
|
||||
|
||||
def _login(self):
|
||||
return "test-token"
|
||||
|
||||
def _directory(self, token):
|
||||
return ([{"id": u} for u in sorted(self.users)],
|
||||
[{"id": i, "displayName": n} for n, i in self.groups.items()])
|
||||
|
||||
def _user(self, token, subject):
|
||||
return {"id": subject, "groups": [{"id": i, "displayName": n} for n, i in self.groups.items()
|
||||
if subject in self.members[i]]}
|
||||
|
||||
def _ensure_group(self, token, groups, name):
|
||||
if name not in self.groups:
|
||||
self.calls.append(("create", name))
|
||||
self.groups[name] = max(self.groups.values()) + 1
|
||||
self.members[self.groups[name]] = set()
|
||||
return self.groups[name]
|
||||
|
||||
def _add_group(self, token, username, group_id):
|
||||
self.calls.append(("add", username, group_id))
|
||||
if not self.drop_add:
|
||||
self.members[group_id].add(username)
|
||||
|
||||
def _remove_group(self, token, username, group_id):
|
||||
self.calls.append(("remove", username, group_id))
|
||||
self.members[group_id].discard(username)
|
||||
|
||||
def _group_members(self, token, group_id):
|
||||
return sorted(self.members[group_id])
|
||||
|
||||
def _gql(self, token, query, variables):
|
||||
if "addUserToGroup" in query:
|
||||
self._add_group(token, variables["userId"], variables["groupId"])
|
||||
return {"addUserToGroup": {"ok": True}}
|
||||
if "deleteGroup" in query:
|
||||
self.calls.append(("delete", variables["id"]))
|
||||
name = next(n for n, i in self.groups.items() if i == variables["id"])
|
||||
del self.groups[name]
|
||||
return {"deleteGroup": {"ok": True}}
|
||||
raise AssertionError(query)
|
||||
|
||||
|
||||
def req(**kw):
|
||||
p = {"groups": ["forge-admins", "forge-coulomb-owners", "forge-coulomb-readers"],
|
||||
"members": {"forge-admins": ["tegwick"], "forge-coulomb-owners": ["tegwick"]},
|
||||
"idempotency_key": "forgejo-netkingdom-oidc-1", "correlation_id": "test"}
|
||||
p.update(kw)
|
||||
return p
|
||||
|
||||
|
||||
class EnsureTests(unittest.TestCase):
|
||||
def test_creates_missing_groups_and_adds_members(self):
|
||||
d = Directory()
|
||||
r = dispatch(d, "/v1/groups/ensure", req())
|
||||
self.assertEqual("groups_ensured", r.status)
|
||||
self.assertFalse(r.resumed)
|
||||
self.assertEqual({"forge-coulomb-owners", "forge-coulomb-readers"},
|
||||
{c[1] for c in d.calls if c[0] == "create"})
|
||||
self.assertIn("tegwick", d.members[d.groups["forge-admins"]])
|
||||
self.assertIn("tegwick", d.members[d.groups["forge-coulomb-owners"]])
|
||||
|
||||
def test_idempotent_and_reports_resumed(self):
|
||||
d = Directory()
|
||||
dispatch(d, "/v1/groups/ensure", req())
|
||||
d.calls.clear()
|
||||
r = dispatch(d, "/v1/groups/ensure", req())
|
||||
self.assertTrue(r.resumed)
|
||||
self.assertEqual([], [c for c in d.calls if c[0] == "create"])
|
||||
|
||||
def test_never_removes_other_memberships(self):
|
||||
d = Directory()
|
||||
d.members[3].add("agent-codex")
|
||||
dispatch(d, "/v1/groups/ensure", req())
|
||||
self.assertIn("agent-codex", d.members[3])
|
||||
self.assertEqual([], [c for c in d.calls if c[0] == "remove"])
|
||||
|
||||
def test_reserved_and_invalid_requests_change_nothing(self):
|
||||
bad = [req(groups=["lldap_admin"]), req(groups=["tenant-x"]), req(groups=["netkingdom-suspended"]),
|
||||
req(groups=["Forge Admins"]), req(groups=[]),
|
||||
req(members={"not-listed": ["tegwick"]}),
|
||||
req(members={"forge-admins": ["Not A User"]}),
|
||||
req(members={"forge-admins": ["nobody"]})]
|
||||
for payload in bad:
|
||||
d = Directory()
|
||||
with self.assertRaises(ValueError, msg=payload):
|
||||
dispatch(d, "/v1/groups/ensure", payload)
|
||||
self.assertEqual([], d.calls, payload)
|
||||
|
||||
def test_failed_readback_never_reports_success(self):
|
||||
d = Directory()
|
||||
d.drop_add = True
|
||||
with self.assertRaises(RuntimeError):
|
||||
dispatch(d, "/v1/groups/ensure", req())
|
||||
|
||||
|
||||
class RevokeTests(unittest.TestCase):
|
||||
def test_removes_only_listed_memberships_and_keeps_groups_by_default(self):
|
||||
d = Directory()
|
||||
d.members[3].add("agent-codex")
|
||||
dispatch(d, "/v1/groups/ensure", req())
|
||||
r = dispatch(d, "/v1/groups/revoke", req(ensure_idempotency_key=req()["idempotency_key"], ensure_correlation_id=req()["correlation_id"]))
|
||||
self.assertEqual("groups_revoked", r.status)
|
||||
self.assertEqual({"agent-codex"}, d.members[d.groups["forge-admins"]])
|
||||
self.assertIn("forge-coulomb-owners", d.groups)
|
||||
|
||||
def test_deletes_only_empty_listed_groups_when_asked(self):
|
||||
d = Directory()
|
||||
d.members[3].add("agent-codex")
|
||||
dispatch(d, "/v1/groups/ensure", req())
|
||||
r = dispatch(d, "/v1/groups/revoke", req(delete_if_empty=True, ensure_idempotency_key=req()["idempotency_key"], ensure_correlation_id=req()["correlation_id"]))
|
||||
self.assertEqual("groups_revoked_and_deleted", r.status)
|
||||
self.assertIn("forge-admins", d.groups)
|
||||
self.assertNotIn("forge-coulomb-owners", d.groups)
|
||||
self.assertNotIn("forge-coulomb-readers", d.groups)
|
||||
self.assertIn("lldap_admin", d.groups)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
# Prepared disposable PostgreSQL compatibility fixture
|
||||
|
||||
Status: prepared and unit checked; not executed or approved. Default
|
||||
`python3 driver.py --plan` reads only the manifest, without loading containment,
|
||||
generating material or calling Docker. Execution requires independent review of
|
||||
the exact manifest/source closure and explicit execution flag/digest. The flag
|
||||
and fixture files confer no authority.
|
||||
|
||||
The fixture reuses the existing `isolated-keycape.py` command/environment guard,
|
||||
containment inspector and exact name/label cleanup. A network-none PostgreSQL
|
||||
anchor and a pinned cached User Engine Python helper share only loopback. Both
|
||||
are nonroot/read-only/capability-free, with private bounded tmpfs, no host ports,
|
||||
mounts or Docker socket, and no pulls/builds/packages. Generated admin/runtime
|
||||
passwords enter through stdin only. Image-specific nonroot UID999/Python/psycopg
|
||||
compatibility is an explicit precondition checked by execution; no cache or
|
||||
runtime claim is made during preparation. Any incompatible image/anonymous
|
||||
volume/error fails closed and still invokes exact cleanup.
|
||||
|
||||
Six sequential Python phases use the actual candidate SQL backend and its
|
||||
normal provisioner path with the **existing synthetic LDAP Directory**. Bootstrap
|
||||
runs the real migration on generated state, checks runtime DDL denial and exact
|
||||
created-only inventories. A separate process reopens and replays/revokes while
|
||||
preserving the complete synthetic prestate. Concurrent reservations must yield
|
||||
one original receipt; stale row CAS cannot advance its head. Two actual database
|
||||
clones then pass the same old checkpoint check concurrently and commit different
|
||||
receipts at the same next counter. Only one external digest CAS may win; a fresh
|
||||
backend on the losing clone must refuse even at that same counter. Lost checkpoint
|
||||
acknowledgment, lost synthetic directory response and a lost reply after actual
|
||||
SQL commit must retain pending/uncertain state and refuse continuation. An actual
|
||||
pg_dump taken after bootstrap is then pg_restored after later journal changes;
|
||||
the external test checkpoint survives. Runtime SQL SELECT must work and show an
|
||||
older restored head before the candidate's restore-quarantine refusal oracle can
|
||||
pass. This prevents an unrelated connection/ACL failure from masquerading as
|
||||
restore protection.
|
||||
|
||||
All fresh names/labels are registered before create. `finally` uses the existing
|
||||
cleanup helper for both resources, requires successful exact absence inventories
|
||||
and retains any Docker uncertainty as failed. PID1 lifetimes and RPC/execution
|
||||
limits are bounded in the manifest. This is not a parent-death/reaper proof.
|
||||
Only safe booleans, public image/source/version pins and cleanup duties leave in
|
||||
a private `/tmp/nk48-postgres-native-review-*/result.json`; synthetic backup bytes
|
||||
are bounded in host memory and never written as an archive file.
|
||||
|
||||
The test checkpoint implementation belongs only to this fixture. Its independent
|
||||
file survives PostgreSQL restore and uses file locking/fsync; it is not offered
|
||||
as native custody, case authority, a journal-recovery API or an evidence broker.
|
||||
The fixture explicitly disposes one definitely pre-LDAP synthetic reservation to
|
||||
move between fault cases; it does not clear an unknown native mutation. All
|
||||
source/fixture passes remain distinct from live CNPG/lease/TLS/backup continuity,
|
||||
protected owner callbacks, actual authoritative tenant scope, native LDAP fencing
|
||||
and production acceptance.
|
||||
|
||||
|
||||
## R2 cached helper selection
|
||||
|
||||
The reviewed R1 preflight failed before `execute`, session allocation or private
|
||||
import because its pinned helper image 1e6547 was absent. No pull or override was
|
||||
used. Frozen manifest/driver/probe/source and the coordinator's structured failure
|
||||
are retained under `revisions/r1`. There is no raw stderr file; the original
|
||||
traceback was returned in tool output only, so no byte-exact stderr artifact is
|
||||
invented.
|
||||
|
||||
The proposed R2 helper is the coordinator-observed cached f239 image, bound by
|
||||
existing published receipts to User Engine source 1bb7cacc. That source uses
|
||||
system pip on official Python 3.12, not a virtualenv. R2 therefore binds every
|
||||
helper launch/exec to `/usr/local/bin/python3`; it has no PATH or alternate-venv
|
||||
fallback. A closed in-container probe must verify the exact executable and import
|
||||
psycopg 3 before candidate source import, reporting the bundled version. Missing
|
||||
or mismatched interpreter/dependency refuses and preserves exact cleanup duties.
|
||||
No container count, limit, privilege, network or cleanup expansion is proposed.
|
||||
Historical producer or rollout acceptance is not a new image/dependency grant.
|
||||
See `helper-selection-r2.json`, `manifest-delta-r2.json` and `preparation-r2.json`.
|
||||
|
||||
|
||||
## R3 bounded failure diagnostics
|
||||
|
||||
R2 executed and failed after containment and interpreter verification (bundled
|
||||
psycopg 3.3.6); all phase checks were empty. Its unchanged receipt and manifest
|
||||
are preserved in `revisions/r2`. The old driver discarded a failed helper result,
|
||||
so the exact failing subphase is unknown. Bootstrap is not asserted as the cause.
|
||||
|
||||
R3 adds fixed public driver/RPC markers and helper NDJSON markers, retained before
|
||||
an oracle raises. PostgreSQL initdb now completes synchronously with only a fixed
|
||||
boolean result before the detached server launch. Bootstrap separately identifies
|
||||
connection readiness, migration, epoch insertion, role/grants, checkpoint and
|
||||
ensure/readback. Failures expose only an allowlisted exception class and optional
|
||||
five-character SQLSTATE, including a wrapped cause; never exception messages,
|
||||
SQL/DSN/password values, command arguments or database logs. Public frames are
|
||||
bounded to 32768 bytes and 96 lines; RPC trace is bounded to 192 fixed rows. The
|
||||
safe result is checkpointed before/after each RPC and on helper failure. This
|
||||
remains a sequential fixture, with unchanged images, authority, backend source,
|
||||
oracles, container/network limits and exact cleanup requirements. No retry or
|
||||
runtime execution is authorized by this diagnostic revision.
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
"""Closed public fixture diagnostics. Never serialize exception text or SQL values."""
|
||||
import json
|
||||
import re
|
||||
|
||||
DRIVER_STAGES = ('session_registered', 'anchor_launch', 'anchor_containment', 'postgres_private_import',
|
||||
'postgres_initdb', 'postgres_launch', 'anchor_identity', 'helper_launch', 'full_containment',
|
||||
'helper_interpreter', 'candidate_import', 'bootstrap', 'restart', 'concurrency', 'fork', 'lost-ack',
|
||||
'restored', 'database_dump', 'database_restore', 'cleanup', 'complete')
|
||||
PROBE_STAGES = ('probe_entry', 'loopback_check', 'phase_dispatch', 'bootstrap_connect_wait',
|
||||
'bootstrap_connected', 'bootstrap_schema', 'bootstrap_migration', 'bootstrap_epoch_insert',
|
||||
'bootstrap_role_create', 'bootstrap_runtime_grants', 'bootstrap_checkpoint_init',
|
||||
'bootstrap_directory_init', 'bootstrap_ensure', 'bootstrap_ensure_readback',
|
||||
'bootstrap_runtime_ddl_refusal', 'bootstrap_complete', 'phase_complete')
|
||||
ERROR_CLASSES = ('GroupProvenanceUnavailable', 'RuntimeError', 'ValueError', 'TypeError', 'KeyError',
|
||||
'FileNotFoundError', 'PermissionError', 'OperationalError', 'ProgrammingError', 'IntegrityError',
|
||||
'InsufficientPrivilege', 'InvalidPassword', 'UndefinedTable', 'UndefinedColumn', 'SyntaxError',
|
||||
'DuplicateTable', 'DuplicateObject', 'InvalidSchemaName', 'DeadlockDetected', 'ConnectionException',
|
||||
'InvalidAuthorizationSpecification', 'OSError', 'TimeoutExpired', 'ImportError',
|
||||
'ModuleNotFoundError', 'JSONDecodeError', 'OtherError')
|
||||
|
||||
|
||||
def error_metadata(error, stage):
|
||||
if stage not in DRIVER_STAGES + PROBE_STAGES:
|
||||
stage = 'phase_dispatch'
|
||||
name = type(error).__name__
|
||||
name = name if name in ERROR_CLASSES else 'OtherError'
|
||||
state = None
|
||||
current = error
|
||||
for _ in range(8):
|
||||
if current is None:
|
||||
break
|
||||
try:
|
||||
candidate = getattr(current, 'sqlstate', None)
|
||||
if isinstance(candidate, str) and re.fullmatch('[0-9A-Z]{5}', candidate):
|
||||
state = candidate
|
||||
break
|
||||
current = current.__cause__ or current.__context__
|
||||
except Exception:
|
||||
break
|
||||
return {'stage': stage, 'exception_class': name, 'sqlstate': state}
|
||||
|
||||
|
||||
def emit_stage(stage):
|
||||
if stage not in PROBE_STAGES:
|
||||
raise RuntimeError('invalid_public_fixture_stage')
|
||||
print(json.dumps({'event': 'stage', 'stage': stage}), flush=True)
|
||||
|
||||
|
||||
def parse_frames(raw, *, require_final=True):
|
||||
if isinstance(raw, bytes):
|
||||
raw = raw.decode('utf-8')
|
||||
if not isinstance(raw, str) or len(raw) > 32768:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
stages, final = [], None
|
||||
lines = raw.splitlines()
|
||||
if len(lines) > 96:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
for line in lines:
|
||||
value = json.loads(line)
|
||||
if not isinstance(value, dict) or final is not None:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
if value.get('event') == 'stage':
|
||||
if set(value) != {'event', 'stage'} or value['stage'] not in PROBE_STAGES:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
stages.append(value['stage'])
|
||||
elif value.get('status') in {'passed', 'failed'} and final is None:
|
||||
if value['status'] == 'failed':
|
||||
if set(value) != {'status', 'failure', 'diagnostic'} or value['failure'] != 'synthetic_pg_oracle_failed':
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
diagnostic = value['diagnostic']
|
||||
if set(diagnostic) != {'stage', 'exception_class', 'sqlstate'} or diagnostic['stage'] not in PROBE_STAGES or diagnostic['exception_class'] not in ERROR_CLASSES:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
if diagnostic['sqlstate'] is not None and not re.fullmatch('[0-9A-Z]{5}', str(diagnostic['sqlstate'])):
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
elif (set(value) != {'status', 'checks', 'psycopg_version', 'active_database'}
|
||||
or not isinstance(value['checks'], dict)
|
||||
or not isinstance(value['psycopg_version'], str)
|
||||
or not re.fullmatch(r'3\.[0-9]+\.[0-9]+(?:[a-z]+[0-9]+)?', value['psycopg_version'])
|
||||
or value['active_database'] not in {'postgres', 'fixture_fork'}):
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
final = value
|
||||
else:
|
||||
raise RuntimeError('invalid_public_fixture_diagnostics')
|
||||
if require_final and final is None:
|
||||
raise RuntimeError('missing_public_fixture_result')
|
||||
return stages, final
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Default is a read-only plan. Docker execution awaits independent exact review."""
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import secrets
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
import uuid
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
MANIFEST = HERE / 'manifest.json'
|
||||
|
||||
# Fixed local diagnostic parser; no dependency or credential discovery.
|
||||
_spec = importlib.util.spec_from_file_location('fixture_diagnostic_protocol', HERE / 'diagnostic_protocol.py')
|
||||
diagnostics = importlib.util.module_from_spec(_spec)
|
||||
_spec.loader.exec_module(diagnostics)
|
||||
|
||||
|
||||
def require(condition):
|
||||
if not condition:
|
||||
raise RuntimeError('bounded_synthetic_fixture_refused')
|
||||
|
||||
|
||||
def reviewed_manifest(expected):
|
||||
raw = MANIFEST.read_bytes()
|
||||
require(expected == hashlib.sha256(raw).hexdigest())
|
||||
manifest = json.loads(raw)
|
||||
for path, digest in manifest['source_sha256'].items():
|
||||
require(bool(re.fullmatch(r'sha256:[0-9a-f]{64}', digest)))
|
||||
require('sha256:' + hashlib.sha256(Path(path).read_bytes()).hexdigest() == digest)
|
||||
return manifest
|
||||
|
||||
|
||||
def load_containment(manifest):
|
||||
spec = importlib.util.spec_from_file_location('reviewed_existing_containment', manifest['containment_helper'])
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def session_plan(manifest, run_id):
|
||||
require(bool(re.fullmatch(r'[a-f0-9]{32}', run_id)))
|
||||
anchor = 'nk49-kc-' + run_id + '-journal-postgres'
|
||||
probe = 'nk49-kc-' + run_id + '-journal-probe'
|
||||
return {'run_id': run_id, 'containers': [anchor, probe], 'worker': anchor, 'issuer': probe,
|
||||
'images': {anchor: manifest['postgres_image'], probe: manifest['python_image']}, 'volumes': {}}
|
||||
|
||||
|
||||
def base_args(session, name, network, user="1000:1000"):
|
||||
uid, gid = user.split(":")
|
||||
return ['docker', 'run', '--detach', '--pull=never', '--name', name, '--network', network,
|
||||
'--label', 'net-kingdom.recovery-run=' + session['run_id'], '--read-only', '--cap-drop', 'ALL',
|
||||
'--security-opt', 'no-new-privileges', '--user', user, '--memory', '384m', '--cpus', '0.5',
|
||||
'--pids-limit', '64', '--log-driver', 'none', '--restart', 'no',
|
||||
'--tmpfs', '/tmp:rw,nosuid,nodev,noexec,size=16777216,uid=' + uid + ',gid=' + gid + ',mode=0700']
|
||||
|
||||
|
||||
def tar_bytes(files):
|
||||
out = io.BytesIO()
|
||||
with tarfile.open(fileobj=out, mode='w') as archive:
|
||||
for name, value in files.items():
|
||||
require(bool(re.fullmatch(r'[a-zA-Z0-9._/-]+', name)) and not name.startswith('/') and '..' not in Path(name).parts)
|
||||
entry = tarfile.TarInfo(name)
|
||||
entry.size = len(value)
|
||||
entry.mode = 0o600
|
||||
entry.uid = entry.gid = 1000
|
||||
archive.addfile(entry, io.BytesIO(value))
|
||||
return out.getvalue()
|
||||
|
||||
|
||||
IMPORT = r'''
|
||||
import base64,json,os,sys
|
||||
from pathlib import Path
|
||||
os.umask(0o077)
|
||||
root=Path('/fixture')
|
||||
(root/'source').mkdir(mode=0o700)
|
||||
(root/'source/migrations').mkdir(mode=0o700)
|
||||
(root/'source/tests').mkdir(mode=0o700)
|
||||
for name,encoded in json.load(sys.stdin).items():
|
||||
if name not in ('source/provisioner.py','source/group_provenance.py','source/group_provenance_postgres.py','source/migrations/group_journal_postgres_v1.sql','source/tests/test_service_groups.py','probe.py','private.json','diagnostic_protocol.py'):raise ValueError()
|
||||
fd=os.open(root/name,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
|
||||
with os.fdopen(fd,'wb') as output:output.write(base64.b64decode(encoded,validate=True))
|
||||
'''
|
||||
|
||||
|
||||
def helper_python(manifest):
|
||||
# Exact producer source installs into official Python's system prefix.
|
||||
# Never fall back to PATH or another interpreter/virtual environment.
|
||||
require(manifest.get('helper_interpreter') == '/usr/local/bin/python3')
|
||||
return manifest['helper_interpreter']
|
||||
|
||||
|
||||
INTERPRETER_PROBE = r"""
|
||||
import json,sys
|
||||
try:
|
||||
import psycopg
|
||||
valid=sys.executable=='/usr/local/bin/python3' and psycopg.__version__.split('.')[0]=='3'
|
||||
print(json.dumps({'helper_ready':valid,'executable':sys.executable,'psycopg_version':psycopg.__version__}))
|
||||
except Exception:
|
||||
print(json.dumps({'helper_ready':False}))
|
||||
"""
|
||||
|
||||
|
||||
def preflight(adapter, manifest):
|
||||
helper_python(manifest)
|
||||
adapter.docker_environment() # Refuse ambient alternate routing before calls.
|
||||
contexts = json.loads(adapter.command(['docker', 'context', 'inspect'], timeout=15))
|
||||
require(len(contexts) == 1 and contexts[0]['Endpoints']['docker']['Host'] == manifest['fixed_endpoint'])
|
||||
facts = json.loads(adapter.command(['docker', 'info', '--format',
|
||||
'{"ID":{{json .ID}},"Name":{{json .Name}},"OSType":{{json .OSType}}}'], timeout=15))
|
||||
require(facts == {'ID': manifest['engine_id'], 'Name': manifest['engine_name'], 'OSType': 'linux'})
|
||||
for image in (manifest['postgres_image'], manifest['python_image']):
|
||||
value = adapter.command(['docker', 'image', 'inspect', '--format', '{{.Id}}', image], timeout=15).decode().strip()
|
||||
require(bool(re.fullmatch(r'sha256:[a-f0-9]{64}', value)))
|
||||
|
||||
|
||||
def retain_helper_result(result, phase, raw, publish):
|
||||
stages, value = diagnostics.parse_frames(raw)
|
||||
safe = {'status': value['status'], 'stages': stages}
|
||||
if value['status'] == 'failed':
|
||||
safe['diagnostic'] = value['diagnostic']
|
||||
result.setdefault('helper_diagnostics', {})[phase] = safe
|
||||
publish() # Persist failure evidence before the phase oracle can raise.
|
||||
return value
|
||||
|
||||
|
||||
def execute(manifest, adapter, result, checkpoint=None):
|
||||
session = session_plan(manifest, uuid.uuid4().hex)
|
||||
result['session'] = {'run_id': session['run_id'], 'containers': session['containers'],
|
||||
'label': 'net-kingdom.recovery-run=' + session['run_id']}
|
||||
python = helper_python(manifest)
|
||||
deadline = time.monotonic() + 210
|
||||
original_command = adapter.command
|
||||
def publish():
|
||||
if checkpoint is not None:
|
||||
try: checkpoint()
|
||||
except OSError: result['receipt_checkpoint_failed'] = True
|
||||
def mark(stage):
|
||||
require(stage in diagnostics.DRIVER_STAGES)
|
||||
result['driver_stage'] = stage
|
||||
publish()
|
||||
def event(argv):
|
||||
require(len(result.setdefault('rpc_trace', [])) < 192)
|
||||
operation = argv[1] if len(argv) > 1 and argv[1] in {'run','exec','rm','container','context','image','info','volume'} else 'other'
|
||||
row = {'stage': result['driver_stage'], 'operation': operation, 'status': 'started'}
|
||||
result['rpc_trace'].append(row)
|
||||
publish()
|
||||
return row
|
||||
def run(argv, **kwargs):
|
||||
row = event(argv)
|
||||
remaining = deadline - time.monotonic()
|
||||
require(remaining > 0)
|
||||
try:
|
||||
value = original_command(argv, timeout=min(50, remaining), **kwargs)
|
||||
row['status'] = 'completed'
|
||||
publish()
|
||||
return value
|
||||
except BaseException as error:
|
||||
row['status'] = 'failed'
|
||||
result['daemon_request_uncertain'] = True
|
||||
result.setdefault('driver_failure', diagnostics.error_metadata(error, result['driver_stage']))
|
||||
# Timeout output can contain complete public progress frames. Never
|
||||
# retain raw stdout/stderr or command/exception text.
|
||||
if result['driver_stage'] in manifest['phase_oracles']:
|
||||
partial = getattr(error, 'stdout', None)
|
||||
if partial:
|
||||
try:
|
||||
stages, _ = diagnostics.parse_frames(partial, require_final=False)
|
||||
result.setdefault('helper_diagnostics', {})[result['driver_stage']] = {'status': 'interrupted', 'stages': stages}
|
||||
except Exception:
|
||||
pass
|
||||
publish()
|
||||
raise
|
||||
mark('session_registered')
|
||||
adapter.command = run
|
||||
try:
|
||||
anchor = session['worker']
|
||||
args = base_args(session, anchor, 'none', user='999:999') + [
|
||||
'--tmpfs', '/var/lib/postgresql:rw,nosuid,nodev,noexec,size=1048576,uid=999,gid=999,mode=0700',
|
||||
'--tmpfs', '/var/lib/postgresql/data:rw,nosuid,nodev,noexec,size=134217728,uid=999,gid=999,mode=0700',
|
||||
'--tmpfs', '/run/fixture:rw,nosuid,nodev,noexec,size=1048576,uid=999,gid=999,mode=0700',
|
||||
'--env', 'PGDATA=/var/lib/postgresql/data', '--entrypoint', '/bin/sh',
|
||||
manifest['postgres_image'], '-c', 'sleep 240']
|
||||
mark('anchor_launch')
|
||||
run(args)
|
||||
mark('anchor_containment')
|
||||
adapter.inspect_session({**session, 'containers': [anchor], 'issuer': anchor}, worker_only=True)
|
||||
admin_password, runtime_password = secrets.token_hex(24), secrets.token_hex(24)
|
||||
mark('postgres_private_import')
|
||||
run(['docker', 'exec', '-i', anchor, 'tar', '-xf', '-', '-C', '/run/fixture'],
|
||||
input=tar_bytes({'password': admin_password.encode()}))
|
||||
mark('postgres_initdb')
|
||||
initialized = json.loads(run(['docker', 'exec', anchor, '/bin/sh', '-c',
|
||||
"if initdb -D /var/lib/postgresql/data --username=fixture_admin --auth-local=trust --auth-host=scram-sha-256 --pwfile=/run/fixture/password > /dev/null 2>&1; then echo '{\"postgres_initdb\":true}'; else echo '{\"postgres_initdb\":false}'; fi"], max_output=256))
|
||||
result['postgres_initdb_complete'] = initialized == {'postgres_initdb': True}
|
||||
publish()
|
||||
require(result['postgres_initdb_complete'])
|
||||
mark('postgres_launch')
|
||||
run(['docker', 'exec', '--detach', anchor, '/bin/sh', '-c',
|
||||
'exec postgres -D /var/lib/postgresql/data -c listen_addresses=127.0.0.1 -c unix_socket_directories=/run/fixture > /dev/null 2>&1'])
|
||||
mark('anchor_identity')
|
||||
anchor_id = run(['docker', 'container', 'inspect', anchor, '--format', '{{.Id}}']).decode().strip()
|
||||
require(bool(re.fullmatch(r'[a-f0-9]{64}', anchor_id)))
|
||||
args = base_args(session, session['issuer'], 'container:' + anchor_id) + [
|
||||
'--tmpfs', '/fixture:rw,nosuid,nodev,noexec,size=16777216,uid=1000,gid=1000,mode=0700',
|
||||
'--entrypoint', python, manifest['python_image'], '-c', 'import time;time.sleep(240)']
|
||||
mark('helper_launch')
|
||||
run(args)
|
||||
mark('full_containment')
|
||||
adapter.inspect_session(session, worker_only=True)
|
||||
result['containment_verified_before_generated_private_import'] = True
|
||||
mark('helper_interpreter')
|
||||
interpreter = json.loads(run(['docker', 'exec', session['issuer'], python, '-c', INTERPRETER_PROBE], max_output=4096))
|
||||
require(interpreter.get('helper_ready') is True and interpreter.get('executable') == python)
|
||||
require(isinstance(interpreter.get('psycopg_version'), str) and re.fullmatch(r'3\.[0-9]+\.[0-9]+(?:[a-z]+[0-9]+)?', interpreter['psycopg_version']))
|
||||
result['helper_interpreter_verified_before_candidate_import'] = True
|
||||
result['helper_psycopg_version'] = interpreter['psycopg_version']
|
||||
files = {'source/' + name: (HERE.parent / 'postimage/identity-provisioner' / name).read_bytes()
|
||||
for name in ('provisioner.py', 'group_provenance.py', 'group_provenance_postgres.py',
|
||||
'migrations/group_journal_postgres_v1.sql')}
|
||||
files['source/tests/test_service_groups.py'] = (HERE / 'test_service_groups.py').read_bytes()
|
||||
files.update({'probe.py': (HERE / 'probe.py').read_bytes(), 'diagnostic_protocol.py': (HERE / 'diagnostic_protocol.py').read_bytes(), 'private.json': json.dumps({
|
||||
'admin_password': admin_password, 'runtime_password': runtime_password}).encode()})
|
||||
mark('candidate_import')
|
||||
run(['docker', 'exec', '-i', session['issuer'], python, '-c', IMPORT],
|
||||
input=json.dumps({k: base64.b64encode(v).decode() for k, v in files.items()}).encode())
|
||||
result['checks'] = {}
|
||||
backup = None
|
||||
active_database = 'postgres'
|
||||
for phase in ('bootstrap', 'restart', 'concurrency', 'fork', 'lost-ack', 'restored'):
|
||||
if phase == 'restored':
|
||||
require(backup is not None)
|
||||
mark('database_restore')
|
||||
run(['docker', 'exec', '-i', anchor, 'pg_restore', '--host', '/run/fixture', '-U', 'fixture_admin',
|
||||
'--dbname', active_database, '--clean', '--if-exists', '--single-transaction', '--exit-on-error', '--no-owner'], input=backup)
|
||||
mark(phase)
|
||||
raw = run(['docker', 'exec', session['issuer'], python, '/fixture/probe.py', phase], max_output=32768)
|
||||
value = retain_helper_result(result, phase, raw, publish)
|
||||
expected = set(manifest['phase_oracles'][phase])
|
||||
require(value.get('status') == 'passed' and set(value.get('checks', {})) == expected)
|
||||
require(all(v is True for v in value['checks'].values()))
|
||||
result['checks'].update(value['checks'])
|
||||
result['psycopg_version'] = value['psycopg_version']
|
||||
require(value.get('active_database') in {'postgres', 'fixture_fork'})
|
||||
active_database = value['active_database']
|
||||
if phase == 'bootstrap':
|
||||
mark('database_dump')
|
||||
backup = run(['docker', 'exec', anchor, 'pg_dump', '--host', '/run/fixture', '-U', 'fixture_admin',
|
||||
'--dbname', 'postgres', '--format=custom', '--no-owner'], max_output=4194304)
|
||||
result['status'] = 'synthetic_postgres_journal_compatibility_passed'
|
||||
mark('complete')
|
||||
except BaseException as error:
|
||||
result.setdefault('driver_failure', diagnostics.error_metadata(error, result['driver_stage']))
|
||||
publish()
|
||||
raise
|
||||
finally:
|
||||
mark('cleanup')
|
||||
def cleanup_command(argv, **kwargs):
|
||||
row = event(argv)
|
||||
try:
|
||||
value = original_command(argv, timeout=15, **kwargs)
|
||||
row['status'] = 'completed'
|
||||
publish()
|
||||
return value
|
||||
except BaseException:
|
||||
result['daemon_request_uncertain'] = True
|
||||
row['status'] = 'failed'
|
||||
publish()
|
||||
raise
|
||||
adapter.command = cleanup_command
|
||||
try:
|
||||
adapter.cleanup(session, result)
|
||||
result['container_cleanup_verified'] = True
|
||||
except BaseException:
|
||||
result['status'] = 'failed'
|
||||
result['container_cleanup_verified'] = False
|
||||
adapter.command = original_command
|
||||
publish()
|
||||
if result.get('daemon_request_uncertain') or result.get('receipt_checkpoint_failed'):
|
||||
result['status'] = 'failed'
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--plan', action='store_true')
|
||||
parser.add_argument('--execute-reviewed-fixture', action='store_true')
|
||||
parser.add_argument('--expected-manifest-sha256')
|
||||
args = parser.parse_args()
|
||||
if not args.execute_reviewed_fixture:
|
||||
print(MANIFEST.read_text())
|
||||
return 0
|
||||
require(not args.plan)
|
||||
manifest = reviewed_manifest(args.expected_manifest_sha256)
|
||||
adapter = load_containment(manifest)
|
||||
preflight(adapter, manifest)
|
||||
os.umask(0o077)
|
||||
output = Path(tempfile.mkdtemp(prefix='nk48-postgres-native-review-', dir='/tmp')) / 'result.json'
|
||||
result = {'schema': 'nk48.synthetic-postgres-journal-result.v1', 'status': 'failed',
|
||||
'manifest_sha256': args.expected_manifest_sha256, 'production_contacted': False,
|
||||
'actual_credentials_used': False, 'native_acceptance_claimed': False}
|
||||
try:
|
||||
execute(manifest, adapter, result, checkpoint=lambda: output.write_text(json.dumps(result, indent=2) + '\n'))
|
||||
except BaseException:
|
||||
result['status'] = 'failed'
|
||||
finally:
|
||||
output.write_text(json.dumps(result, indent=2) + '\n')
|
||||
print(json.dumps({'status': result['status'], 'safe_result': str(output)}))
|
||||
return int(result['status'] != 'synthetic_postgres_journal_compatibility_passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"schema": "nk48.synthetic-postgres-journal-result.v1",
|
||||
"status": "failed",
|
||||
"manifest_sha256": "78f2b0bbd7639780ca3a3a689310527f31e7e14c1fdb497a48bac4f69bce4093",
|
||||
"production_contacted": false,
|
||||
"actual_credentials_used": false,
|
||||
"native_acceptance_claimed": false,
|
||||
"session": {
|
||||
"run_id": "a20a5eed1ee54968b61e8a1cb2043b30",
|
||||
"containers": [
|
||||
"nk49-kc-a20a5eed1ee54968b61e8a1cb2043b30-journal-postgres",
|
||||
"nk49-kc-a20a5eed1ee54968b61e8a1cb2043b30-journal-probe"
|
||||
],
|
||||
"label": "net-kingdom.recovery-run=a20a5eed1ee54968b61e8a1cb2043b30"
|
||||
},
|
||||
"driver_stage": "cleanup",
|
||||
"rpc_trace": [
|
||||
{
|
||||
"stage": "anchor_launch",
|
||||
"operation": "run",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "anchor_containment",
|
||||
"operation": "context",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "anchor_containment",
|
||||
"operation": "info",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "anchor_containment",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "anchor_containment",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "postgres_private_import",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "postgres_initdb",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "postgres_launch",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "anchor_identity",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "helper_launch",
|
||||
"operation": "run",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "full_containment",
|
||||
"operation": "context",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "full_containment",
|
||||
"operation": "info",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "full_containment",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "full_containment",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "full_containment",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "helper_interpreter",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "candidate_import",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "bootstrap",
|
||||
"operation": "exec",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "rm",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "rm",
|
||||
"status": "completed"
|
||||
},
|
||||
{
|
||||
"stage": "cleanup",
|
||||
"operation": "container",
|
||||
"status": "completed"
|
||||
}
|
||||
],
|
||||
"postgres_initdb_complete": true,
|
||||
"containment_verified_before_generated_private_import": true,
|
||||
"helper_interpreter_verified_before_candidate_import": true,
|
||||
"helper_psycopg_version": "3.3.6",
|
||||
"checks": {},
|
||||
"helper_diagnostics": {
|
||||
"bootstrap": {
|
||||
"status": "failed",
|
||||
"stages": [
|
||||
"probe_entry",
|
||||
"loopback_check",
|
||||
"phase_dispatch",
|
||||
"bootstrap_connect_wait",
|
||||
"bootstrap_connected",
|
||||
"bootstrap_schema",
|
||||
"bootstrap_migration",
|
||||
"bootstrap_epoch_insert",
|
||||
"bootstrap_role_create",
|
||||
"bootstrap_runtime_grants",
|
||||
"bootstrap_checkpoint_init",
|
||||
"bootstrap_directory_init",
|
||||
"bootstrap_ensure"
|
||||
],
|
||||
"diagnostic": {
|
||||
"stage": "bootstrap_ensure",
|
||||
"exception_class": "RuntimeError",
|
||||
"sqlstate": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"driver_failure": {
|
||||
"stage": "bootstrap",
|
||||
"exception_class": "RuntimeError",
|
||||
"sqlstate": null
|
||||
},
|
||||
"container_cleanup_obligations": [
|
||||
{
|
||||
"container": "nk49-kc-a20a5eed1ee54968b61e8a1cb2043b30-journal-probe",
|
||||
"status": "absence_verified"
|
||||
},
|
||||
{
|
||||
"container": "nk49-kc-a20a5eed1ee54968b61e8a1cb2043b30-journal-postgres",
|
||||
"status": "absence_verified"
|
||||
}
|
||||
],
|
||||
"container_cleanup_verified": true
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"status": "fixture_helper_successor_proposed_not_executed",
|
||||
"selected_cached_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b",
|
||||
"producer_revision": "1bb7cacc98479f56f6b1c28c000479efd260a944",
|
||||
"producer_containerfile_sha256": "668a90308bb6307f95a8c5439e5d751f81bf1f7126755e26373f3523f07c858a",
|
||||
"producer_containerfile_public_source": "https://forgejo.coulomb.social/coulomb/user-engine/src/commit/1bb7cacc98479f56f6b1c28c000479efd260a944/Containerfile",
|
||||
"provenance_sources": [
|
||||
{
|
||||
"repo": "rapp-user-engine",
|
||||
"path": "docs/evidence/2026-10-11-published-readback-proposal/published-receipt.json",
|
||||
"revision": "a42c68f8701880bfeb93e28dd812fcf3691568a3",
|
||||
"sha256": "2f84959e36d442860fb2b5a5817bb707c4f8372797a3ef8dd0d5cab956af86ce",
|
||||
"public_source": "https://forgejo.coulomb.social/coulomb/rapp-user-engine/src/commit/a42c68f8701880bfeb93e28dd812fcf3691568a3/docs/evidence/2026-10-11-published-readback-proposal/published-receipt.json"
|
||||
},
|
||||
{
|
||||
"repo": "user-engine",
|
||||
"path": "docs/evidence/2026-10-11-one-app-catalogue-executor/isolated-postgres-receipt.json",
|
||||
"revision": "9557d6f867fe03c546aa26655f84b4192109981d",
|
||||
"sha256": "964cc3755e96330bf6d1ada63410f8adfdb3c45602e0446369d6dddace412405",
|
||||
"public_source": "https://forgejo.coulomb.social/coulomb/user-engine/src/commit/9557d6f867fe03c546aa26655f84b4192109981d/docs/evidence/2026-10-11-one-app-catalogue-executor/isolated-postgres-receipt.json"
|
||||
}
|
||||
],
|
||||
"selection_reason": "coordinator reports exact image cached; published source receipt binds image to producer; prior owner fixture used exact image for psycopg dependencies",
|
||||
"interpreter": "/usr/local/bin/python3",
|
||||
"interpreter_binding": "producer officialPython3.12+systempip runtime, no virtualenv; execution must verify exact sys.executable/psycopg3 before candidate import, no PATHfallback",
|
||||
"not_claimed": [
|
||||
"worker did not query Docker or create/run containers",
|
||||
"bundled psycopg version not observed yet; must be reported by exact helper",
|
||||
"historical image risk/production acceptance not transferred",
|
||||
"cached helper is not a future identity-provisioner image/dependency admission"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"from_manifest_sha256": "4a816f74f857dfbebb5eaa0de3d792c91cd5e0f52caf458c2f7789583507ee31",
|
||||
"to_manifest_sha256": "8ac8547b43ac1679acadd422993bc3fbce842a6c315a728af4d452734910240b",
|
||||
"source_patch_unchanged": "503e3d0217e6be25df8a58f3968cab7c00cdd9933d0fee2a66ef951fc4aae2b1",
|
||||
"helper_image": {
|
||||
"before": "forgejo.coulomb.social/coulomb/user-engine@sha256:1e6547c40c93c49f01438edcf76c20f561ccebc110220bae020723505aa56c70",
|
||||
"after": "forgejo.coulomb.social/coulomb/user-engine@sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b"
|
||||
},
|
||||
"helper_interpreter": {
|
||||
"before": "PATH python3",
|
||||
"after": "/usr/local/bin/python3",
|
||||
"verification": "closed exact sys.executable/psycopg3 probe before candidate source import"
|
||||
},
|
||||
"unchanged_contracts": {
|
||||
"postgres_image": true,
|
||||
"fixed_endpoint": true,
|
||||
"engine_id": true,
|
||||
"engine_name": true,
|
||||
"limits": true,
|
||||
"resources": true,
|
||||
"phase_oracles": true,
|
||||
"cleanup": true,
|
||||
"network": true,
|
||||
"pull_policy": true
|
||||
},
|
||||
"worker_docker_calls": false,
|
||||
"execution_requires_new_independent_review": true
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"schema": "nk48.fixture-manifest-delta.v1",
|
||||
"previous_manifest_sha256": "8ac8547b43ac1679acadd422993bc3fbce842a6c315a728af4d452734910240b",
|
||||
"successor_manifest_sha256": "78f2b0bbd7639780ca3a3a689310527f31e7e14c1fdb497a48bac4f69bce4093",
|
||||
"changed_top_level_fields": [
|
||||
"diagnostics",
|
||||
"r2_outcome",
|
||||
"revision",
|
||||
"source_sha256",
|
||||
"supersedes_manifest_sha256"
|
||||
],
|
||||
"unchanged_contract_fields": [
|
||||
"candidate_patch_sha256",
|
||||
"containment_helper",
|
||||
"fixed_endpoint",
|
||||
"engine_id",
|
||||
"engine_name",
|
||||
"postgres_image",
|
||||
"python_image",
|
||||
"helper_interpreter",
|
||||
"limits",
|
||||
"resources",
|
||||
"phase_oracles",
|
||||
"cleanup",
|
||||
"network",
|
||||
"pull_policy"
|
||||
],
|
||||
"runtime_executed": false
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"schema": "nk48.postgres-journal-fixture-proposal.v1",
|
||||
"status": "prepared_not_executed_not_approved",
|
||||
"source_sha256": {
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py": "sha256:04237939df1887df8a201181d87ebfacfa25855401b64f4998a3921111bd91eb",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/probe.py": "sha256:39eea88a246a799945da72d022200dd9e15662b765bbc896a4edbdc2f8bfd5a0",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_driver.py": "sha256:ab735b251ca70c709035b62d501dd46f842792fcb2c3b69888ae2aad698cff6e",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/test_service_groups.py": "sha256:815571fd92c0001dacbe250640acd99b2f2872b4171519ed54f8f469ee90aafe",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/source-manifest.json": "sha256:0c16e19415ad93b6ca73c92a05c205d263adde8a780c8c33119cd5f9d40f4752",
|
||||
"/home/worsch/net-kingdom/tools/bootstrap-recovery/isolated-keycape.py": "sha256:f67627b7d1647d75187dd899874efe22383bc91de5d97dabb9801076668ab3c2",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/provisioner.py": "sha256:19d1b837cbafeeacb1aa815856cd68091e6702281d72b1d860230fa320e1725b",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/group_provenance.py": "sha256:405e08eaaab7c21d618dff4db5970607806f32ab6c461effb81c737e32182b10",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/group_provenance_postgres.py": "sha256:55282af26f95af66ff8d444d38d0bcbcf3ee58a615adedfff8da5db2b0716889",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/postimage/identity-provisioner/migrations/group_journal_postgres_v1.sql": "sha256:3cdf82194d6f1e2dd865b0c7e7b8a14447fda8c49151148134a0d40a80fb46ac",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/README.md": "sha256:b327ac993c72517b268850a9ca59344e73d825effabd3271d809243866dcd293",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/README.md": "sha256:4bf4678a1f1c70fbd3d4ffa77c70821be0372f03210b2164a2895fc99ca6a6b9",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/helper-selection-r2.json": "sha256:ec5464c0d5310028ab91073f985183af01da2d194358fad412d197dbf90106d9",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/producer-Containerfile.txt": "sha256:668a90308bb6307f95a8c5439e5d751f81bf1f7126755e26373f3523f07c858a",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/diagnostic_protocol.py": "sha256:d141fc00e18906bcdbf6cf5542e219580668daaaced6c57cbab179132f7c6a61"
|
||||
},
|
||||
"candidate_patch_sha256": "503e3d0217e6be25df8a58f3968cab7c00cdd9933d0fee2a66ef951fc4aae2b1",
|
||||
"containment_helper": "/home/worsch/net-kingdom/tools/bootstrap-recovery/isolated-keycape.py",
|
||||
"fixed_endpoint": "unix:///var/run/docker.sock",
|
||||
"engine_id": "74f93783-6cef-43a8-a3c4-f62586cd3bc1",
|
||||
"engine_name": "bnt-lap001",
|
||||
"postgres_image": "postgres@sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70",
|
||||
"python_image": "forgejo.coulomb.social/coulomb/user-engine@sha256:f239bbf332b8eb87320c86390fddca1dcb4f7c628a7af75e79dee8058d446a7b",
|
||||
"image_pin_origin": "PostgreSQL pin unchanged; cached f239 helper selected from coordinator inventory and tied to UserEngine producer1bb7cacc by existing published receipt/previous owner PostgreSQL fixture; fixture-only selection, no future image admission",
|
||||
"pull_policy": "never",
|
||||
"network": "network-none",
|
||||
"scope": "real disposable PostgreSQL backend SQL/migration/concurrency/restore compatibility; LDAP is explicitly the existing synthetic test Directory, not actual native LDAP in this fixture",
|
||||
"limits": {
|
||||
"max_service_containers": 2,
|
||||
"host_ports": 0,
|
||||
"host_mounts": 0,
|
||||
"named_or_anonymous_volumes": 0,
|
||||
"memory_bytes_per_container": 402653184,
|
||||
"cpus_per_container": 0.5,
|
||||
"pids_per_container": 64,
|
||||
"postgres_tmpfs_bytes": 153092096,
|
||||
"helper_tmpfs_bytes": 33554432,
|
||||
"backup_in_host_memory_max_bytes": 4194304,
|
||||
"container_lifetime_seconds": 240,
|
||||
"execution_deadline_seconds": 210,
|
||||
"docker_rpc_timeout_seconds": 50,
|
||||
"preflight_rpc_timeout_seconds": 15,
|
||||
"cleanup_rpc_timeout_seconds": 15,
|
||||
"cleanup_total_bound_seconds": 120,
|
||||
"new_cloud_spend_eur": 0
|
||||
},
|
||||
"compatibility_preconditions": [
|
||||
"exact cached image/engine pins reverified by admitted preflight, never pulled",
|
||||
"PostgreSQL image supports nonroot UID999 with initdb/postgres/pg_dump/pg_restore in PATH; failed compatibility refuses/cleans",
|
||||
"User Engine f239 producer source uses official Python system prefix; exact /usr/local/bin/python3 and bundled psycopg3 must pass closed probe before candidate import, no PATH/venv fallback or downloads",
|
||||
"all image-declared volumes must be masked by private tmpfs; existing containment guard refuses any anonymous volume before private import"
|
||||
],
|
||||
"resources": {
|
||||
"container_names": "nk49-kc-<fresh32hex>-journal-{postgres,probe}",
|
||||
"exact_label": "net-kingdom.recovery-run=<same-fresh32hex>",
|
||||
"private_state": "generated admin/runtime passwords, source/migration, synthetic directory, external test checkpoint within private container tmpfs",
|
||||
"safe_result": "private /tmp/nk48-postgres-native-review-<suffix>/result.json"
|
||||
},
|
||||
"phase_oracles": {
|
||||
"bootstrap": [
|
||||
"only_loopback_no_external_routes",
|
||||
"actual_postgresql_journal_ensure",
|
||||
"explicit_scope_context_retained",
|
||||
"confirmed_inventory_excludes_preexisting",
|
||||
"runtime_ddl_denied"
|
||||
],
|
||||
"restart": [
|
||||
"only_loopback_no_external_routes",
|
||||
"separate_process_postgres_restart_and_revoke",
|
||||
"preexisting_shared_state_preserved",
|
||||
"foreign_context_receipt_refused"
|
||||
],
|
||||
"concurrency": [
|
||||
"only_loopback_no_external_routes",
|
||||
"concurrent_original_reservation_exactly_once",
|
||||
"stale_version_update_refused_without_head_advance"
|
||||
],
|
||||
"lost-ack": [
|
||||
"only_loopback_no_external_routes",
|
||||
"lost_checkpoint_ack_retains_pending_reservation",
|
||||
"reopened_uncertainty_refuses_ldap_effects",
|
||||
"active_fence_survives_process_reopen",
|
||||
"lost_synthetic_directory_response_retains_postgres_intent",
|
||||
"lost_sql_commit_reply_retains_committed_head",
|
||||
"reopened_stale_checkpoint_refuses_continuation"
|
||||
],
|
||||
"restored": [
|
||||
"only_loopback_no_external_routes",
|
||||
"actual_pg_restore_behind_checkpoint_detected",
|
||||
"restored_store_cannot_gain_replay_authority"
|
||||
],
|
||||
"fork": [
|
||||
"only_loopback_no_external_routes",
|
||||
"concurrent_divergent_clones_same_counter_verified",
|
||||
"independent_exact_transition_cas_selects_one_winner",
|
||||
"reopened_loser_clone_refused_at_same_counter"
|
||||
]
|
||||
},
|
||||
"restore": "actual generated pg_dump after bootstrap; winner database advances through reopen/concurrency/fork/uncertainty; actual pg_restore of original archive while independent content checkpoint survives; runtime SELECT must work and show older restored head before refusal can pass",
|
||||
"cleanup": "finally reuses exact name/label cleanup for both preregistered containers, including failed/lost-create replies; successful explicit absence required; any Docker uncertainty remains failed; no prune/global removal",
|
||||
"not_claimed": [
|
||||
"CNPG HA, native runtime storage/backup/restore admission",
|
||||
"authoritative tenant/case/PDP/evidence/session owner callbacks",
|
||||
"external LDAP writer fencing/CAS/ABA safety",
|
||||
"production endpoint activation, credentials or owner assent",
|
||||
"KeyCape hard-parent-loss reaper proof"
|
||||
],
|
||||
"forbidden": [
|
||||
"Docker commands before exact independent review",
|
||||
"pull/build/push, host networking/ports/mounts, production APIs",
|
||||
"real database/LDAP, identity archives, credentials from environment/custody",
|
||||
"clearing production restore quarantine or manufacturing scope/authority"
|
||||
],
|
||||
"execution_command": [
|
||||
"python3",
|
||||
"/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-16/group-postgres-r1/native-fixture/driver.py",
|
||||
"--execute-reviewed-fixture",
|
||||
"--expected-manifest-sha256",
|
||||
"<reviewed-exact-manifest-digest>"
|
||||
],
|
||||
"fork": "actual PostgreSQL CREATE DATABASE TEMPLATE clone; both stores pass exact same old external marker check under test barrier, commit different bodies at same counter, race external strict CAS; exactly one wins and fresh loser store refuses; no directory effect in this direct reservation case",
|
||||
"revision": "r3-bounded-stage-diagnostics",
|
||||
"helper_interpreter": "/usr/local/bin/python3",
|
||||
"supersedes_manifest_sha256": "8ac8547b43ac1679acadd422993bc3fbce842a6c315a728af4d452734910240b",
|
||||
"r1_preflight": "failed missing helper before execute/session/private import; preserved structured failure, no raw stderr file exists",
|
||||
"r2_outcome": "failed after containment/interpreter verification; checks empty, exact helper subphase unavailable; byte-exact safe receipt retained revisions/r2/execution-result.json; cleanup absence verified",
|
||||
"diagnostics": {
|
||||
"driver_stage": "fixed closed vocabulary before each RPC/bootstep, checkpointed result",
|
||||
"probe_stage": "fixed closed vocabulary NDJSON before bootstrap substeps, failed final retained before oracle refusal",
|
||||
"exceptions": "allowlisted class and optional five-character SQLSTATE only, never values/logs/exception messages",
|
||||
"max_probe_output_bytes": 32768,
|
||||
"max_probe_frames": 96,
|
||||
"max_rpc_trace_rows": 192,
|
||||
"initdb": "synchronous fixed boolean completion before detached server launch"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue