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

View file

@ -149,6 +149,12 @@ class PolicyClient:
if not isinstance(data.get("id"), str) or not data["id"] or len(data["id"]) > 256:
raise ValueError()
binding, provenance = data["binding"], data["provenance"]
# FLEX-DEC-2026-019: advisory readback decisions are never act
# authorization. Canonical enforcement purpose is absent (Go
# omitempty) or the exact empty string; malformed values fail closed.
purpose = binding.get("purpose", "")
if type(purpose) is not str or purpose != "":
raise ValueError()
if (binding["submitted_request_digest"] != submitted
or not DIGEST.fullmatch(binding["request_digest"])
or binding["tenant"] != request["tenant"] or binding["action"] != request["action"]

View file

@ -54,3 +54,66 @@ def test_initial_audience_collaborates_across_revisions_with_real_pdp(workspace,
reopened.retrieve(foreign, memo.id)
assert calls and all(c['known'] and c['audiences'] == ['flex-auth'] for c in calls)
assert (fixture / 'pin.json').read_bytes() == pins
@pytest.mark.parametrize("stale_field", ["version", "package_digest"])
def test_stale_consumer_pin_fails_closed_until_exact_owner_repin(workspace, tmp_path, signing_key, stale_field):
binary = os.environ.get('INFD_FLEX_AUTH_BINARY')
source = os.environ.get('INFD_FLEX_AUTH_SOURCE')
if not binary or not source:
pytest.skip('set INFD_FLEX_AUTH_BINARY and INFD_FLEX_AUTH_SOURCE for candidate owner policy')
fixture = Path(source) / 'examples/informed-decision-lifecycle'
with flex_component(binary, tmp_path, signing_key, fixture=fixture) as (pdp, calls):
repo, memo = workspace
app = LifecycleService(repo, pdp)
hub = Caller(Attribution('tenant:platform', Actor('system:serviceaccount:state-hub:state-hub', ActorKind.SERVICE)),
time.time() + 600, dumps({'principal_type_source': 'authentication-derived',
'tenant_source': 'registration-supplied', 'authentication_audience': 'informed-decision-lifecycle',
'workload_uid': 'synthetic-verified-uid'}))
current = getattr(pdp, stale_field)
setattr(pdp, stale_field, 'v1' if stale_field == 'version' else
'sha256:cee6a79f74e81d0d389a2c2e2451a92be3434ea9dfcee4986e8c7e6cfdbb1827')
with pytest.raises(LifecycleError) as refused:
app.register(hub, memo, key='repin-proof')
assert refused.value.status == 503 and refused.value.code == 'invalid_decision'
with repo.store._connection() as db:
assert db.execute('SELECT count(*) FROM memos').fetchone()[0] == 0
assert db.execute('SELECT count(*) FROM lifecycle_events').fetchone()[0] == 0
setattr(pdp, stale_field, current)
app.register(hub, memo, key='repin-proof')
assert app.retrieve(hub, memo.id) == memo
assert calls and all(c['known'] and c['audiences'] == ['flex-auth'] for c in calls)
def test_real_enforcement_response_with_readback_purpose_cannot_authorize(workspace, tmp_path, signing_key):
binary = os.environ.get('INFD_FLEX_AUTH_BINARY')
source = os.environ.get('INFD_FLEX_AUTH_SOURCE')
if not binary or not source:
pytest.skip('set INFD_FLEX_AUTH_BINARY and INFD_FLEX_AUTH_SOURCE for candidate owner policy')
fixture = Path(source) / 'examples/informed-decision-lifecycle'
with flex_component(binary, tmp_path, signing_key, fixture=fixture) as (pdp, calls):
repo, memo = workspace
app = LifecycleService(repo, pdp)
hub = Caller(Attribution('tenant:platform', Actor('system:serviceaccount:state-hub:state-hub', ActorKind.SERVICE)),
time.time() + 600, dumps({'principal_type_source': 'authentication-derived',
'tenant_source': 'registration-supplied', 'authentication_audience': 'informed-decision-lifecycle',
'workload_uid': 'synthetic-verified-uid'}))
original = pdp.transport.request
def advisory_response(*args, **kwargs):
status, data = original(*args, **kwargs)
assert status == 200 and data['effect'] == 'allow'
assert data['binding'].get('purpose', '') == ''
# Keep the real request binding, digest, pins and TTL unchanged:
# even an otherwise exact allow cannot cross the advisory boundary.
data['binding']['purpose'] = 'readback'
return status, data
pdp.transport.request = advisory_response
with pytest.raises(LifecycleError) as refused:
app.register(hub, memo, key='advisory-proof')
assert refused.value.status == 503 and refused.value.code == 'invalid_decision'
with repo.store._connection() as db:
assert db.execute('SELECT count(*) FROM memos').fetchone()[0] == 0
assert db.execute('SELECT count(*) FROM lifecycle_events').fetchone()[0] == 0
pdp.transport.request = original
app.register(hub, memo, key='advisory-proof')
assert app.retrieve(hub, memo.id) == memo

View file

@ -113,3 +113,17 @@ def test_transport_and_missing_caller_have_typed_observations():
client=policy(Broken());assert client.check(request()).outcome=="policy_unavailable"
client.token_provider=lambda:None
assert client.check(request()).outcome=="caller_unavailable"
@pytest.mark.parametrize("purpose", ["", "readback", "enforcement", "unknown", None, False, 0, [], {}])
def test_binding_purpose_only_allows_exact_enforcement_form(purpose):
transport = PolicyTransport()
transport.change = lambda d: d["binding"].update(purpose=purpose)
observed = policy(transport).check(request())
if type(purpose) is str and purpose == "":
observed.require_current(time.time())
assert json.loads(observed.decision_json)["binding"]["purpose"] == ""
else:
assert observed.outcome == "invalid_decision" and observed.decision_json is None
with pytest.raises(PolicyError):
observed.require_current(time.time())

View file

@ -0,0 +1,107 @@
---
id: INFD-WP-0009
type: workplan
title: "Repair lifecycle consumer pin after Access Engine v2 policy release"
domain: infotech
repo: informed-decision
status: active
owner: codex
topic_slug: netkingdom
created: "2026-10-10"
updated: "2026-10-11"
flavor: implementation
depends_on: []
related:
- FLEX-WP-0035
- INFD-WP-0007
state_hub_workstream_id: "77eaeabf-0644-595f-b3d5-1168e6a294c4"
---
## Diagnose and prepare exact config repair
```task
id: INFD-WP-0009-T01
status: done
priority: high
state_hub_task_id: "5c30c308-8fe2-5956-b0c1-66c80bcb07e9"
```
Native State Hub lifecycle submission returns HTTP 503 `invalid_decision`.
The service remains Ready but its configured lifecycle package is v1/cee6a79;
Access Engine lifecycle provider now runs v2/da01e045 after FLEX-WP-0035.
The envelope contract remains `flex-auth.decision-record.v1`; this is a policy
pin mismatch, not an envelope compatibility migration. Keep exact package,
version and digest enforcement. Source preparation and regression evidence are
in `docs/evidence/2026-10-10-lifecycle-consumer-pin-repair.md`.
## Release and verify the config-only repair
```task
id: INFD-WP-0009-T02
status: done
priority: high
needs_human: false
state_hub_task_id: "1e6684d7-7676-5c70-b701-9dad124b4f07"
```
Apply only the reviewed immutable runtime ConfigMap and Deployment reference
through the existing owner GitOps path after exact scoped supervisor approval.
Image, schema, credentials, audience and review policy remain pinned. Retain
server dry-run/diff, readiness and native State Hub register/publish receipts;
retry with stable write keys, do not infer success from readiness. Root agent
owns live evidence and approval recording. This is a repair of the shipped
initial lifecycle, separate from INFD-WP-0008 expansion.
**Verified 2026-10-11:** founder approved exact config-only revision
`7db8485ffecdebfabd77a9daec085553c74bf0bf`. Manual Argo Application operation
synced only `informed-decision-runtime-6c221878d051` and Deployment, prune false.
Application Succeeded/Healthy; generation24 observed24 Ready1; `/readyz`200;
consumer v2/da01e045 and unchanged image7f5c6cb verified. Native lifecycle
registration and publication succeeded (events28/29). A later review request
rejected packet validation; this separate caller error does not undo verified
authoring. Custodian evidence records original approval, execution and rollback:
`docs/evidence/2026-10-10-security-consolidation/infd-pin-repair-preflight.json`.
## Explicitly reject advisory purpose at consumer boundary
```task
id: INFD-WP-0009-T03
status: done
priority: medium
state_hub_task_id: "ee798d7d-5e60-5247-9c9b-285a7f65cf11"
```
PolicyClient now validates binding purpose: absent or exact empty string only;
readback and malformed purposes are refused. Nine purpose variants and a real
provider response contamination test preserve the binding, pin, expiry and
obligations gates. This task covers source implementation and regression tests.
The current runtime image lacks this patch. Service build/native release belongs
to T04, separate from the config-only T02 repair.
## Release the advisory-purpose source correction
```task
id: INFD-WP-0009-T04
status: progress
priority: medium
state_hub_task_id: "0e1bb868-7bc2-5d4d-953c-e0edaf660ad0"
```
Build and scan a new service image including T03, retain provenance and backup
compatibility evidence, prepare a separate scoped image proposal, and verify
native binding/refusal after the owner's approved release. Do not fold an image
change into the config-only T02 proposal. Current runtime 7f5c6cb remains exact.
**Prepared 2026-10-11:** narrow single-module image overlay on immutable running
7f5c6cb; exact source policy SHA-256 9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62.
Local image/index b8b58944786015a5375304241c149d1dab885eb539d6d8945f56b01a3c2bc821
is built and retained, unpublished and undeployed. Base layer prefix and runtime
configuration are preserved; installed-image 10 purpose variants pass; 13
container checks include restart and synthetic consistent backup/restore. A
fresh Trivy database scan has zero HIGH/CRITICAL findings, with its Alpine EOL
warning retained. See `docs/evidence/2026-10-11-purpose-hardening/README.md`.
Remaining gates: source-bound registry publication and exact digest verification,
fresh native snapshot/restore compatibility, owner GitOps server dry-run/diff,
separate scoped supervisor approval and native post-release binding/refusal.
No release approval or actual production-data restore is inferred from fixtures.