Reject advisory policy envelopes before lifecycle effects

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
tegwick 2026-10-11 02:22:07 +02:00
parent caab9a90ec
commit 1e0df2f4a3
13 changed files with 2854 additions and 0 deletions

View file

@ -0,0 +1,122 @@
# Lifecycle consumer pin repair — 2026-10-10
Initial preparation is retained below. The config-only repair was approved and
verified live on 2026-10-11; the dated execution appendix distinguishes it from
the separate, unpublished purpose-hardening image. No human disposition is claimed.
Root agent observed native State Hub `POST /v1/decisions` returning HTTP 503
`invalid_decision`, and read only fixed runtime policy fields: lifecycle package
`informed-decision.lifecycle`, version `v1`, digest
`sha256:cee6a79f74e81d0d389a2c2e2451a92be3434ea9dfcee4986e8c7e6cfdbb1827`.
The provider owner pin is now version `v2`, digest
`sha256:da01e045fc108adec6b489e5d51f7e57366793016c5a64776963780a6aae457c`.
Access Engine `pkg/api/canonical.go` defines and emits only envelope contract
`flex-auth.decision-record.v1`. FLEX-WP-0035 adds advisory binding purpose
`readback`; enforcement binding purpose stays absent. Lifecycle policy version
v2 must not be mistaken for envelope contract version v2. Informed Decision's
existing exact package/version/digest check correctly refuses a stale pin.
Readiness does not prove that a consumer pin matches a provider release.
Root's sanitized native provider probe returned HTTP 200, envelope contract
`flex-auth.decision-record.v1`, binding purpose absent, matched policy version
v2, provenance lifecycle package v2/da01e045, and submitted digest match true.
Consumer runtime remained v1/cee6a79. Native pod Ready=1 and image 7f5c6cb.
Reported CPU requested headroom: 3460m/4000m (86% allocated); this repair retains
5m request and Recreate, and does not add a concurrent pod.
## Exact proposed action
Stable proposal: `INFD-WP-0009-T02/config-repin-v1`.
Update the lifecycle pin in `deploy/lifecycle/admission.candidate.json` and
`deploy/lifecycle/service.candidate.json`. Replace immutable runtime ConfigMap
`informed-decision-runtime-c81577741a09` with
`informed-decision-runtime-6c221878d051`, and update the Deployment's
`configuration` volume reference to the new name. Same namespace and service.
The runtime JSON differs only in lifecycle version/digest.
Service image remains
`forgejo.coulomb.social/coulomb/informed-decision@sha256:7f5c6cb29c9f70a026bfe30e72934274ddc69261676cf6d476f81dd32812d173`.
The existing image needs no source parser patch to consume this owner policy.
Local inspected service source HEAD is `bee5ad9af60ab21bcc88423307ce02cc2ab5631b`;
local provider source HEAD is `f5fad7c2851708be41c82891430deffe5f4af0ac`.
No schema migration, credentials, review-policy pin, audience or network
change. Initial lifecycle repair does not implement expansion INFD-WP-0008.
## Verification
Real Access Engine HTTP component with synthetic caller custody, current owner
lifecycle fixture and existing PolicyClient: three tests passed (one collaboration
flow, two stale-pin regressions). Each stale version or digest produces exact
503 `invalid_decision`, writes no memo/lifecycle event, then succeeds with the
exact current owner pin. This proves restoring compatible policy pins suffices;
no parser relaxation is needed. Staged ConfigMap exactly matches the established
renderer output for the repinned admission input; retained customizations in
the existing Deployment are preserved.
Before execution: retain exact live Deployment, old runtime ConfigMap, current
image digest and database evidence counts; verify live runtime JSON differs from
candidate only as reviewed. Run server-side dry-run and diff through the current
owner GitOps release path and retain their results. Check railiance01 headroom:
existing request is 5m CPU, unchanged; Recreate stops the single writer first.
Expect a brief interruption. No concurrent writer is admitted.
After execution: verify exact same image and new ConfigMap reference, rollout
completion and readyz; prove native State Hub registration/publication with
stable write keys and retained provider observations/receipts. Ready alone is
insufficient. Human acceptance remains a separate human act.
## Rollback and risk
Rollback: restore the old Deployment configuration volume reference and its
retained immutable ConfigMap through the same GitOps path. Preserve the existing
schema-5 database and any new evidence; do not restore an old database merely
to revert policy pins. The old pin intentionally returns to fail-closed 503
against the v2 provider; coordinate owner rollback only if the provider itself
must be reverted. No provider rollback belongs to this proposal.
No cloud/resource expansion or paid inference; expected incremental cost EUR 0.
Risk: brief service interruption and continued refusal if the runtime provider
pin differs; verification stops closure until native receipts exist.
## Source hashes
See `2026-10-10-lifecycle-consumer-pin-repair.sources.json` for exact original and
proposed release artifact SHA-256 values. Supervisor approval, approved digest,
executed digest and verified outcome must be recorded by the root executor;
this packet supplies preparation only.
## Separate source hardening (not in config-only live proposal)
PolicyClient now explicitly refuses advisory or malformed binding purpose; only
absence or the exact empty string is the canonical enforcement form. Current
service image 7f5c6cb does not contain this patch. Unit coverage includes nine
purpose variants; a real provider allow envelope with unchanged binding digest,
package pins and TTL is injected with `purpose=readback` and must refuse the
write, then the original enforcement response succeeds. These tests cover the
missing consumer guard without accepting an unpublished envelope v2 contract.
INFD-WP-0009-T04 retains separate image preparation/native release work. T02's
same-image config repair remains unchanged.
Validation after source hardening: 73 policy/purpose, real-provider component
and deployment-render tests passed; 21 lifecycle-service checks passed. Review
controller component checks (53) require its other owner binary and were skipped;
they are not counted as verified. No live hardening rollout is claimed.
## Config-only execution appendix — 2026-10-11
Founder approved exact revision `7db8485ffecdebfabd77a9daec085553c74bf0bf`.
The existing manual Argo Application synced only the new runtime ConfigMap and
Deployment, with prune disabled. Application Succeeded/Healthy; generation24
observed24 Ready1 and `/readyz`200 were verified. Lifecycle consumer version
v2/da01e045 and unchanged service image7f5c6cb were observed. Native State Hub
registration/publication succeeded with stable write keys; review delivery later
completed after correcting a separate caller packet-validation error.
Custodian retains exact approval, rendered digest, executed JSON patch, outcomes
and rollback in `docs/evidence/2026-10-10-security-consolidation/`. Original
preparation hashes above remain historical evidence, not rewritten approval.
Purpose-hardening T04 preparation now has a built, scanned local image and
synthetic installed/backup checks in `2026-10-11-purpose-hardening/README.md`.
That image is neither published nor deployed; config-only approval does not
cover it.

View file

@ -0,0 +1,32 @@
[
{
"repo": "informed-decision",
"path": "deploy/lifecycle/admission.candidate.json",
"original_sha256": "fc5d6fb8ab833f268427d05f01c9c40db4b7fdd30840a5bdd6660dc6277e3cba",
"staged_sha256": "22f830736ee56f6ed9b62f47d243e4e24a1b68263b261e78e2f1cfcb85adbd50"
},
{
"repo": "informed-decision",
"path": "deploy/lifecycle/service.candidate.json",
"original_sha256": "fcfd29e0ed438b8fc3dd2e719db5a914ec3d947775bf38cae8deeb5d2269e954",
"staged_sha256": "7a676532836affd6d75c7f1c41a5f937d8c8c809f63e3264ccce612730d5ba7b"
},
{
"repo": "informed-decision",
"path": "tests/test_lifecycle_policy_component.py",
"original_sha256": "5fc8e57f499ca0c2882d4fcbfc5a5f6af46315d34b3d03d9a3a6dd95e225487b",
"staged_sha256": "7a08fece31ddb5fd80ccdb67c5fc2821e328d8751e33c55714fd398982f71039"
},
{
"repo": "informed-decision",
"path": "informed_decision/policy.py",
"original_sha256": "a00b93b34caee5f190dea750c53b7bca819b993b8ef2f499ef9525d7b8c11842",
"staged_sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62"
},
{
"repo": "informed-decision",
"path": "tests/test_policy_client.py",
"original_sha256": "7e3285e7b0381612f40c13a2f2b8f92d6520a4415b44437bc7538cf87f5adbf7",
"staged_sha256": "2d743fa7f624f667b714be488197cda0cc82056dee44b5457e28786f09447622"
}
]

View file

@ -0,0 +1,5 @@
FROM forgejo.coulomb.social/coulomb/informed-decision@sha256:7f5c6cb29c9f70a026bfe30e72934274ddc69261676cf6d476f81dd32812d173
LABEL org.opencontainers.image.base.digest="sha256:7f5c6cb29c9f70a026bfe30e72934274ddc69261676cf6d476f81dd32812d173"
LABEL social.coulomb.source.policy.sha256="9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62"
LABEL social.coulomb.source.base.revision="7db8485ffecdebfabd77a9daec085553c74bf0bf"
COPY policy.py /opt/venv/lib/python3.12/site-packages/informed_decision/policy.py

View file

@ -0,0 +1,62 @@
# Advisory-purpose hardening image preparation — 2026-10-11
INFD-WP-0009-T04 remains in progress. This candidate is locally built and tested;
it has no registry publication or deployment receipt. Proposal ID:
`INFD-WP-0009-T04/purpose-overlay-v1`. No live action is authorized by this packet.
Base: `forgejo.coulomb.social/coulomb/informed-decision@sha256:7f5c6cb29c9f70a026bfe30e72934274ddc69261676cf6d476f81dd32812d173`.
Base installed policy.py equals HEAD7db8485 policy SHA-256a00b93b34caee5f190dea750c53b7bca819b993b8ef2f499ef9525d7b8c11842.
Overlay source SHA-2569d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62.
Only policy.py is added as one layer. Dependency, schema/store, runtime user,
entrypoint, env and ports remain exact. `provenance.json` binds source and build
recipe; source patch remains uncommitted during preparation and no whole-tree
commit provenance is claimed.
Local candidate `informed-decision:purpose-hardening-20261011` has index digest
`sha256:b8b58944786015a5375304241c149d1dab885eb539d6d8945f56b01a3c2bc821`,
platform manifest918bbdce7337b716edfeb99b9343037dd141bb870aa3f32d7e4394c410c65b7d,
configf187897d7322f10e06d53c153a6fa7e6f7403b47a9b59cf6edd12285ec479310.
These local digests are not registry receipts. Verify eventual published digest
and installed source before authoring a final admitted Deployment pin.
Evidence: `checks.json` proves base layer/config equality and fresh scan DB
(2026-10-10 updated18:59Z, downloaded23:32Z), zero HIGH/CRITICAL findings.
`scan.json` retains package findings; scanner warns Alpine3.24 is not on its
EOL list. Vulnerability scan is bounded and not a universal security claim.
`container.json` retains 13 checks, including private single writer, uncertain
submission preservation, restart, consistent backup and separate synthetic
restore. All temporary containers/volumes were removed. `installed-purpose.py`
passes 10 installed-client forms: absent and empty allow, eight other/advisory
forms refuse. Native identity/PDP/approval/audit proof is not claimed.
Rebuild: make a fresh private temporary context, copy exact policy.py from owner
source and this Containerfile to it, check both hashes against provenance.json,
then `docker build --network none --pull=false -f <context>/Containerfile -t
informed-decision:purpose-hardening-20261011 <context>`. Use only the immutable
base and these files; normal repo .dockerignore excludes a direct module copy.
No package download or broader dirty-source wheel enters this overlay.
## Concrete proposed release
After registry publication/verification, update only admission.candidate.json
image and rendered service.candidate.json Deployment image, preserving current
ConfigMap6c221878d051, schema5, review/lifecycle policy pins, requester custody,
audience, network, PVC, 5m request, replicas1 and Recreate. Retain exact source
revision/digest and server dry-run/diff through manual Argo before approval.
Obtain separate supervisor approval of that concrete immutable image revision;
the config-only7db8485 approval cannot be reused.
Before cutover retain metadata-only counts and unique consistent native snapshot
in protected custody; verify restore with the candidate on a separate protected
volume without printing record content or credentials. Synthetic restore here
proves mechanism only. Same-schema rollback retains current DB/evidence and
restores image7f5c6cb through owner Argo; no data downgrade or replay is intended.
Risk: brief Recreate interruption, no concurrent writer; incremental EUR0 for
existing infrastructure, no paid inference. This source correction refuses an
advisory envelope before effects while preserving exact current enforcement.
After approved release verify exact image/config, health, current pins and native
register/publication with stable keys; inject a native-provider-derived advisory
response in an isolated installed-client probe and prove refusal before writes.
Do not contaminate production PDP config or create human approvals to test this
guard. Reconcile lifecycle/audit receipts and retain failed/uncertain outcomes.

View file

@ -0,0 +1,27 @@
{
"candidate_image_id": "sha256:b8b58944786015a5375304241c149d1dab885eb539d6d8945f56b01a3c2bc821",
"repo_digests": [
"informed-decision@sha256:b8b58944786015a5375304241c149d1dab885eb539d6d8945f56b01a3c2bc821"
],
"base_layers_preserved": true,
"additional_layers": 1,
"runtime_config_preserved": {
"User": true,
"Entrypoint": true,
"Cmd": true,
"Env": true,
"ExposedPorts": true,
"WorkingDir": true
},
"scanner": "aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969",
"db": {
"Version": 2,
"NextUpdate": "2026-10-11T18:59:50.889010572Z",
"UpdatedAt": "2026-10-10T18:59:50.889010923Z",
"DownloadedAt": "2026-10-10T23:32:32.380033131Z"
},
"high_critical": [],
"scan_scope": "candidate OS/package vulnerabilities; not security attestation; scan warning Alpine3.24 not on EOL list",
"published": false,
"deployed": false
}

View file

@ -0,0 +1,32 @@
{
"status": "passed",
"image_id": "sha256:b8b58944786015a5375304241c149d1dab885eb539d6d8945f56b01a3c2bc821",
"repo_digests": [
"informed-decision@sha256:b8b58944786015a5375304241c149d1dab885eb539d6d8945f56b01a3c2bc821"
],
"checks_passed": 13,
"checks": [
"installed container entrypoint serves with projected configuration",
"non-root read-only runtime has no external network or published port",
"private config/database modes enforced; package installer absent",
"configured review refuses anonymous access and reports audit-unready status",
"private listener refuses anonymous lifecycle access and exposes no browser routes",
"public listener exposes no private workload route",
"a second writer cannot acquire the active evidence volume",
"synthetic uncertain intent and undelivered evidence remain explicit",
"consistent backup created while serving; overwrite refused",
"SIGTERM closes the serving writer without forced kill",
"restart preserves immutable content and unresolved submission without retry",
"separate restored volume preserves exact content and unresolved state",
"incomplete owner configuration exits before serving"
],
"fixture_only": true,
"external_network": "none",
"content_snapshot_sha256": "6ff72dc6578d8a4c0803ec4330e6144ae18c3182e29ab5506b039cedefb468ea",
"native_identity_policy_audit_proven": false,
"published": false,
"deployed": false,
"factory_attempts": 0,
"paid_model_calls": 0,
"cleanup_complete": true
}

View file

@ -0,0 +1,21 @@
import copy,json,time
from datetime import datetime,timezone
from informed_decision.policy import PolicyClient,CONTRACT,submitted_digest
pin='sha256:'+'b'*64
req={'id':'image-purpose-fixture','tenant':'tenant:platform','subject':{'id':'synthetic','type':'service','tenant':'tenant:platform'},'resource':{'id':'memo:fixture','type':'memo','system':'informed-decision','tenant':'tenant:platform'},'action':'register','context':{}}
class Transport:
def request(self,*args,**kwargs):
now=time.time(); stamp=lambda t:datetime.fromtimestamp(t,timezone.utc).isoformat()
b={**copy.deepcopy({k:req[k] for k in ('tenant','subject','action','resource','context')}),'request_digest':'sha256:'+'e'*64,'submitted_request_digest':submitted_digest(req)}
if self.purpose!='ABSENT':b['purpose']=self.purpose
d={'id':'synthetic-decision','contract_version':CONTRACT,'request_id':req['id'],'effect':'allow','matched_policy_version':'v1','subject':req['subject'],'resource':req['resource'],'binding':b,'lifetime':{'kind':'ttl','not_before':stamp(now-1),'expires_at':stamp(now+60)},'provenance':{'policy_package':'fixture','policy_version':'v1','policy_package_digest':pin,'registry_snapshot_digest':'sha256:'+'c'*64,'evaluator':'flex-auth/fixture','decision_time':stamp(now)}}
return 200,d
checks=[]
for purpose in ('ABSENT','','readback','enforcement','unknown',None,False,0,[],{}):
t=Transport();t.purpose=purpose
c=PolicyClient('https://fixture.invalid',lambda:'synthetic-only',package='fixture',version='v1',package_digest=pin,transport=t)
o=c.check(req)
if purpose in ('ABSENT',''):assert o.outcome=='allow';o.require_current(time.time())
else:assert o.outcome=='invalid_decision' and o.decision_json is None
checks.append(str(purpose))
print(json.dumps({'installed_image_purpose_variants':len(checks),'binding_forms_allowed':2,'advisory_or_malformed_refused':8,'synthetic_only':True,'native_provider':False}))

View file

@ -0,0 +1,9 @@
{
"source_base_revision": "7db8485ffecdebfabd77a9daec085553c74bf0bf",
"base_image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:7f5c6cb29c9f70a026bfe30e72934274ddc69261676cf6d476f81dd32812d173",
"base_policy_sha256": "a00b93b34caee5f190dea750c53b7bca819b993b8ef2f499ef9525d7b8c11842",
"policy_sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62",
"containerfile_sha256": "42277e419014a56c0d24399a3aa45b2b0b27a7c22e0d225110007fa71460d345",
"scope": "single policy.py overlay; source correction uncommitted; not a claim whole worktree committed",
"tag": "informed-decision:purpose-hardening-20261011"
}

File diff suppressed because it is too large Load diff