docs: preserve reviewed NetKingdom session artifacts
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Reject generated-only commits / generated-commit-guard (push) Successful in 16s

This commit is contained in:
codex 2026-11-01 22:40:29 +01:00
parent a99b47b95f
commit a4c1663558
47 changed files with 7599 additions and 0 deletions

View file

@ -0,0 +1,37 @@
# Access signer rollout preparation R1
Existing work: FLEX-WP-0024-T06. Access deployment preparation only; no cluster/GitOps owner assent, founder approval, custody, trust admission or live action is asserted. Source checks pass. All native gates remain open.
Exact source is Access `84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0`, rendered from a disposable `git archive`, not the working tree. `render-evidence.json` pins every chart/value/policy/registry input and output. The image receipt at `../image-84c78e0/candidate-receipt.json` independently records ordinary main-push CI402 success, pulled source-label verification and public registry agreement for `forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f`. Historical run400 failure remains retained separately.
The canonical local managed package is d52f811/tree d4fbad7, readiness draft, no reef bindings. Its extraction2b/current05a pin remains unchanged. This is a proposed separately reviewed signer successor, not execution through the extraction-only package command and not proof of central/private availability or placement. No owner repository was modified.
`activation-values.proposed.yaml` overlays exact retained `values/secrets-engine.yaml` using current `charts/flex-auth`. `source-render.diff` shows all changes against the same chart's retained render: immutable image, signer mount/flags, single-writer Recreate, bounded durable outbox and a new PVC. Service, ServiceAccount, RBAC, NetworkPolicy and ingress posture are byte-equivalent as parsed objects. Existing caller enforce/audience flex-auth/SA binding, lifecycle policy/registry paths and resource requests/limits remain equal. Audit delivery stays disabled; no task source is enabled; no Secret is generated. Kid and seed Secret name are proposed coordinates, not current custody or public trust admission.
Reproduce locally:
```sh
python3 docs/changes/netkingdom-target-2026-10-16/access-native-admission/rollout-r1/prepare.py --image-receipt docs/changes/netkingdom-target-2026-10-16/access-native-admission/image-84c78e0/candidate-receipt.json
```
`source-deployment-projection.json` is produced with the actual Platform native adapter `deployment_projection` and `encoded` functions, whose module hash is pinned. Its source-rendered SHA256 is in `render-evidence.json`. It includes fixed args, immutable image, SA, UID/GID/fsGroup65532, seed0440 (`288` decimal) directory mount, reviewer projection and PVC mount. Both proposed stopped and ready variants have the same projection. This is not a native stopped-fence receipt: the exact future observed stopped Deployment projection must equal this hash before Platform can use it. Kubernetes defaulting/admission mutation or any drift must be reconciled and reviewed, not silently normalized.
The chart intentionally rejects outbox replicaCount other than1. `stopped-source-render.yaml` is a proposed maintenance state derived from the one-replica render by changing only Deployment replicas to0; it is not a claim that Helm can render replicas0. The future Access/cluster reviewed release path must install the candidate pod template while stopped without letting Helm or another writer restart it. No apply, scale, Helm command or native dry-run was executed. Source rendering is not server dry-run or native diff.
## Proposed exact native order and ownership boundaries
1. Founder and distinct Access, Platform, Secrets, cluster/GitOps owners review the exact successor scope and source/image/config hashes. Capture the actual prechange Helm revision/history and allowlisted nonsecret config. Raw Helm values, Secret objects/annotations/errors, tokens, private key material and credential-bearing logs must not enter shared evidence. Refresh verified node92.205.62.239, kube-system UIDa553c742-0115-43d4-99a4-a5ca56fe0786, exact Deployment UID, namespace, chart/release/controller ownership, current CPU/memory/free disk and namespace resource quota/limit-range disposition. Existing metadata is dated15:20/15:25UTC, not a current headroom claim.
2. Open an independently reviewed exclusive maintenance window. Founder abort owner; Access controls release and protected consumers; cluster owner freezes competing Helm/GitOps sync and other writers under its own authority. Record actual controller identities, prior state and restoration criteria; no controller identity is invented here. Stop affected protected consumer acceptance first. Refuse any HPA target or second signer workload. Install exact reviewed candidate template at replicas0 through the separately approved maintenance path. Wait for observedGeneration to catch up and no Deployment replicas, live/terminating owned pods, signer-SA pods or seed-mount pods. Retain owned ReplicaSets at replicas0 and verify by ownerReference UID, not labels alone. Hold fence through custody/public trust/delivery; recheck immediately before CAS.
3. Platform's separate attended action requires its real admitted CCR0050 read lane, five exact scope-bound independent dispositions, exact founder identity, absolute expiry, current source/image/projection/window/trust hashes and independently authenticated receipts. This artifact supplies none of those approvals. The writer generates its seed in memory; agents do not read/generate it. Ambiguous outcomes preserve fence and unknown receipt; no CAS0 replay or remint.
4. Independently authenticate Platform public receipt and Secrets public admission before reopening signed acceptance. Platform owns delivery manifests/role readback. Access owns seed mount exact Secret/key0440 and startup kid; Secrets owns exact installed bundle/verifier/trust/caller. Candidate64MiB bounded outbox must use the proposed PVC. Prepare source-only PVC request now; creation/binding remains separately authorized. WaitForFirstConsumer means no PV identity can be predicted now. Allow the reviewed binding/start step with consumers still fenced, capture actual claim/PV UIDs and node affinity, and have cluster owner set and verify the exact bound PV reclaim policy Retain before durable acceptance.
5. Under exact reviewed action restore replicaCount1 with Recreate and verify no concurrent writer. Require Ready within120s and total recovery decision within10m. Failures keep consumers fenced. Refresh image/pod UID/SA/startup flags/mounts/UID/GID and exact source-rendered projection equality, then capture native signed lifecycle ALLOW and DENY plus original durable records using the independent Secrets verifier. Require wrong/missing/expired/audience/subject caller refusals, wrong-tenant and task refusal, restart signature/sequence/cursor preservation and separately admitted lock/capacity/unwritable/fsync negative checks. No protected backend effect or audit receiver acceptance is part of this initial proof.
## Storage and rollback
The exact PVC request is `flex-auth/flex-auth-secrets-engine-outbox`, `1Gi` (1073741824 bytes), `ReadWriteOnce`, `local-path`, Helm `resource-policy: keep`. Existing receipt showed zero namespace PVCs, local-path WaitForFirstConsumer/Delete. Namespace storage quota and actual free filesystem bytes are unverified and remain native preflight gates. Requested capacity is neither a hard filesystem quota nor backup/node-loss protection. The process source hard ceiling is67108864 bytes (64MiB); storage class behavior does not enforce it. Exact requested storage versus namespace remaining quota and actual node free bytes must be recorded before create; no synthetic quota limit is asserted.
Helmkeep protects against Helm deletion, not Kubernetes manual deletion or local-path PV Delete. Review actual bound PV name/UID/claimRef UID, capacity, node affinity and Retain mutation; do not guess a PV UID/name or patch an arbitrary claim-selected object. No PVC/PV is created and no Retain mutation is performed here. Backup/restore and independent evidence continuity remain unverified.
Rollback: fence consumers, stop candidate Deployment, observe all owned/SA/seed-mount pods gone and single-writer lock released, retain PVC/logs/cursor/signatures/receipts. Record candidate failure. Only restore the captured prior Helm revision after format compatibility and exact image/config are independently reviewed. Prechange revision number is currently unknown, so no executable rollback with a guessed revision is supplied. Prior05a restores unsigned evaluation availability only and cannot satisfy the strict signed consumer; do not mount or mutate committed new outbox evidence with an unreviewed predecessor. If compatibility or exclusion is uncertain, stay unavailable with evidence preserved. Never uninstall, delete PVC/PV, reset cursor/log, weaken caller/trust/policy or restore an old seed. Key compromise requires public trust withdrawal and separately reviewed forward-only rotation. After CAS ambiguity retain original unknown outcome and use Platform's fresh reviewed read-only recovery.
Source preparation adds EUR0 infrastructure and makes no resource allocation. Proposed native addition is one1Gi local-path request on the existing node, current25m CPU/32Mi request and300m CPU/192Mi limits unchanged. Native spend approval and a quantified loss ceiling are absent. Availability interruption, node-local evidence loss and forged lifecycle decisions after signer compromise require founder disposition; no autopilot grant follows from this proposal.

View file

@ -0,0 +1,237 @@
---
# Source: flex-auth/templates/networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: flex-auth-secrets-engine
policyTypes:
- Ingress
- Egress
egress:
- ports:
- port: 443
protocol: TCP
- port: 6443
protocol: TCP
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: secrets-engine
podSelector:
matchLabels:
app.kubernetes.io/name: secrets-engine
ports:
- port: 8080
protocol: TCP
---
# Source: flex-auth/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
automountServiceAccountToken: false
---
# Source: flex-auth/templates/outbox-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: flex-auth-secrets-engine-outbox
annotations:
helm.sh/resource-policy: keep
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
accessModes:
- ReadWriteOnce
storageClassName: "local-path"
resources:
requests:
storage: "1Gi"
---
# Source: flex-auth/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flex-auth-secrets-engine-tokenreviewer
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
rules:
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
---
# Source: flex-auth/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flex-auth-secrets-engine-tokenreviewer
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flex-auth-secrets-engine-tokenreviewer
subjects:
- kind: ServiceAccount
name: flex-auth-secrets-engine
namespace: flex-auth
---
# Source: flex-auth/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
selector:
app.kubernetes.io/name: flex-auth-secrets-engine
ports:
- name: http
port: 8080
targetPort: http
---
# Source: flex-auth/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: flex-auth-secrets-engine
template:
metadata:
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
automountServiceAccountToken: false
serviceAccountName: flex-auth-secrets-engine
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: flex-auth
image: forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f
imagePullPolicy: IfNotPresent
args:
- serve
- --addr
- 0.0.0.0:8080
- --registry
- /opt/flex-auth/examples/secrets-engine/registry_snapshot.json
- --policy
- /opt/flex-auth/examples/secrets-engine/policy_package.md
- --envelope-signing-key-file
- "/var/run/secrets/decision-envelope-signing/seed"
- --envelope-signing-kid
- "flex-auth-lifecycle-20261016-r1"
- --outbox-max-bytes
- "67108864"
- --outbox-dir
- "/var/lib/flex-auth/outbox"
- --emission-source
- "flex-auth.secrets-engine"
- --caller-audience
- "flex-auth"
- --caller-auth-mode
- "enforce"
- --caller-kubernetes-url
- "https://10.43.0.1"
- --caller-binding
- "secrets-engine=system:serviceaccount:secrets-engine:secrets-engine"
ports:
- name: http
containerPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 20
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 5
resources:
limits:
cpu: 300m
memory: 192Mi
requests:
cpu: 25m
memory: 32Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- mountPath: "/var/run/secrets/decision-envelope-signing"
name: flex-auth-envelope-signing
readOnly: true
- mountPath: "/var/lib/flex-auth/outbox"
name: flex-auth-outbox
- mountPath: /var/run/secrets/flex-auth-reviewer
name: flex-auth-reviewer
readOnly: true
volumes:
- name: flex-auth-envelope-signing
secret:
secretName: "flex-auth-secrets-engine-envelope-signing"
defaultMode: 0440
items:
- key: "seed"
path: seed
- name: flex-auth-outbox
persistentVolumeClaim:
claimName: "flex-auth-secrets-engine-outbox"
- name: flex-auth-reviewer
projected:
defaultMode: 0440
sources:
- serviceAccountToken:
expirationSeconds: 3600
path: token
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt

View file

@ -0,0 +1,20 @@
image:
repository: forgejo.coulomb.social/coulomb/flex-auth
digest: sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f
replicaCount: 1
envelopeSigning:
enabled: true
kid: flex-auth-lifecycle-20261016-r1
secretName: flex-auth-secrets-engine-envelope-signing
secretKey: seed
mountPath: /var/run/secrets/decision-envelope-signing
outbox:
enabled: true
source: flex-auth.secrets-engine
mountPath: /var/lib/flex-auth/outbox
size: 1Gi
maxBytes: 67108864
storageClassName: local-path
existingClaim: ''
auditDelivery:
enabled: false

View file

@ -0,0 +1,60 @@
{
"schema": "access-engine.signer-exclusive-window-proposal.v1",
"proposal_id": "FLEX24-SIGNER-ROLLOUT-20261016-R1",
"status": "proposed-incomplete-native-window-not-held",
"owner_work": "FLEX-WP-0024-T06",
"target": {
"cluster": "railiance01",
"node_ip": "92.205.62.239",
"kube_system_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
"namespace": "flex-auth",
"deployment": "flex-auth-secrets-engine",
"observed_predecessor_deployment_uid": "f83a5f5c-7dd7-4aac-bc02-d447e423e6ae",
"service_account": "flex-auth-secrets-engine"
},
"executor": null,
"abort_owner": "founder",
"access_preparation_owner": "access-engine",
"cluster_gitops_disposition": null,
"founder_disposition": null,
"absolute_window_start": null,
"absolute_window_end": null,
"ready_timeout_seconds": 120,
"recovery_decision_seconds": 600,
"competing_controllers": null,
"actual_prechange_helm_revision": null,
"consumer_stop_receipt": null,
"native_stopped_fence_receipt": null,
"conditions": [
"Protected consumers fenced before template/stop or custody changes",
"Exact Deployment replicas0; observedGeneration equals generation; no status replicas",
"All owned ReplicaSets spec/status replicas0; no live or terminating owned pods",
"No signer-SA or seed-mount pods anywhere in target namespace; no HPA targeting signer",
"Freeze each actual competing Helm/GitOps/automation writer; record exact identity/prior state/restore criteria",
"Native stopped Deployment projection equals reviewed source projection immediately before custody CAS",
"Remain fenced until independently authenticated public trust/delivery/runtime proof",
"Any uncertain write/readback/identity/volume or readiness outcome keeps fence closed"
],
"storage": {
"pvc": "flex-auth-secrets-engine-outbox",
"request": "1Gi",
"request_bytes": 1073741824,
"outbox_hard_ceiling_bytes": 67108864,
"storage_class": "local-path",
"helm_keep": true,
"pv_reclaim_required": "Retain",
"pv_name": null,
"pv_uid": null,
"claim_uid": null,
"namespace_storage_quota_remaining_bytes": null,
"node_free_filesystem_bytes": null,
"binding": "WaitForFirstConsumer; exact actual binding and Retain verification required before durable acceptance"
},
"cost": {
"source_preparation_incremental_eur": 0,
"native_spend_authority": false,
"native_incremental_resources": "one1Gi local-path request; unchanged25m/32Mi workload request",
"risk_loss_ceiling_eur": null
},
"authority_granted": false
}

View file

@ -0,0 +1,20 @@
{
"schema": "access-engine.signer-rollout-preparation.v1",
"proposal_id": "FLEX24-SIGNER-ROLLOUT-20261016-R1",
"role": "access-engine-deployment-preparation-only",
"disposition": "source-checks-pass-native-gates-open",
"cluster_gitops_assent": false,
"execution_authorized": false,
"files": {
"README.md": "sha256:09ea9c7c9026fdd0cb5f995a440b5b71be08b9bad1145df1057a58acecc56da8",
"activation-source-render.yaml": "sha256:b7edecb8ec50a6d2db7623a3d962dbece46de81d3809d5f73b3aca20ff76b8c7",
"activation-values.proposed.yaml": "sha256:e76a30967add800ed8b588368757e7a5fa0ce4d8305ef658b5a4a08e377eb5b8",
"exclusive-window.proposed.json": "sha256:98ad9c7a5661ce7b3a47cad061a03c6d55ca200b54c96c3f257b644c0739bd19",
"prepare.py": "sha256:d15db1603808e1881d6244c536588d5dd255c7b02a95fbd313ff75ea51e752b7",
"render-evidence.json": "sha256:07363f3003cede57d992e7edab2d96e7012e01ccf31fc55f50a6a4ed74cd3bcf",
"retained-source-render.yaml": "sha256:f979f7d05d5b0a54e3b8ac7c413aa4d401139f991b43412cdbde3d63249b0dd9",
"source-deployment-projection.json": "sha256:056b6bf842713fb9a328dae901401d6867da47da68bfb8a4904bb0d7b8d9e912",
"source-render.diff": "sha256:77480a093011aba0e75afc49bc32c6d9ec4b4a9131d87b201f0d5cea9fc15888",
"stopped-source-render.yaml": "sha256:be1c431b07477f4d19baf24874a23556c594328b2bb2b5f52af8ccc483d44133"
}
}

View file

@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Offline source render only. Image receipt is deliberately a separate input."""
import argparse
import difflib
import hashlib
import importlib.util
import io
import json
from pathlib import Path
import re
import subprocess
import sys
import tarfile
import tempfile
import yaml
HERE = Path(__file__).resolve().parent
SOURCE = Path('/home/worsch/access-engine')
REVISION = '84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0'
NATIVE = Path('/home/worsch/railiance-platform/scripts/flex_auth_signer_native.py')
PATHS = ['charts/flex-auth', 'values/secrets-engine.yaml',
'examples/secrets-engine/policy_package.md', 'examples/secrets-engine/registry_snapshot.json']
def sha(data):
return 'sha256:' + hashlib.sha256(data).hexdigest()
def write_json(name, value):
(HERE / name).write_text(json.dumps(value, indent=2) + '\n')
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--image-receipt', type=Path)
args = parser.parse_args()
receipt = json.loads(args.image_receipt.read_text()) if args.image_receipt else None
# A pending shape render has no immutable candidate and is unusable for admission.
digest = 'sha256:' + '0' * 64
if receipt:
assert receipt['source_revision'] == REVISION
assert receipt['ordinary_push'] is True and receipt['terminal_status'] == 'success'
digest = receipt['manifest_digest']
assert receipt['publisher_receipt']['revision'] == REVISION
assert receipt['independent_public_registry_package']['explicit_revision_label'] == REVISION
assert receipt['independent_public_registry_package']['sole_manifest_digest'] == digest
assert re.fullmatch(r'sha256:[0-9a-f]{64}', digest) and digest != 'sha256:' + '0' * 64
overlay = {
'image': {'repository': 'forgejo.coulomb.social/coulomb/flex-auth', 'digest': digest},
'replicaCount': 1,
'envelopeSigning': {'enabled': True, 'kid': 'flex-auth-lifecycle-20261016-r1',
'secretName': 'flex-auth-secrets-engine-envelope-signing', 'secretKey': 'seed',
'mountPath': '/var/run/secrets/decision-envelope-signing'},
'outbox': {'enabled': True, 'source': 'flex-auth.secrets-engine',
'mountPath': '/var/lib/flex-auth/outbox', 'size': '1Gi', 'maxBytes': 67108864,
'storageClassName': 'local-path', 'existingClaim': ''},
'auditDelivery': {'enabled': False},
}
(HERE / 'activation-values.proposed.yaml').write_text(yaml.safe_dump(overlay, sort_keys=False))
archive = subprocess.run(['git', '-C', str(SOURCE), 'archive', REVISION, *PATHS],
capture_output=True, check=True).stdout
with tempfile.TemporaryDirectory(prefix='access-signer-rollout-r1-') as directory:
source = Path(directory)
with tarfile.open(fileobj=io.BytesIO(archive)) as tar:
files = {m.name: sha(tar.extractfile(m).read()) for m in tar.getmembers() if m.isfile()}
tar.extractall(source, filter='data')
base = ['helm', 'template', 'flex-auth-secrets-engine', str(source / 'charts/flex-auth'),
'--namespace', 'flex-auth', '-f', str(source / 'values/secrets-engine.yaml')]
retained = subprocess.run(base, capture_output=True, check=True, text=True).stdout
def render(replicas):
return subprocess.run(base + ['-f', str(HERE / 'activation-values.proposed.yaml'),
'--set', 'replicaCount=' + str(replicas)], capture_output=True, check=True, text=True).stdout
ready = render(1)
# Chart intentionally refuses outbox replicas!=1. Stopping is a distinct
# owner-approved scale action, not an unsafe relaxation of that guard.
stopped_docs = [d for d in yaml.safe_load_all(ready) if d]
next(d for d in stopped_docs if d['kind'] == 'Deployment')['spec']['replicas'] = 0
stopped = yaml.safe_dump_all(stopped_docs, sort_keys=False)
(HERE / 'retained-source-render.yaml').write_text(retained)
(HERE / 'activation-source-render.yaml').write_text(ready)
(HERE / 'stopped-source-render.yaml').write_text(stopped)
(HERE / 'source-render.diff').write_text(''.join(difflib.unified_diff(retained.splitlines(True),
ready.splitlines(True), fromfile='retained84', tofile='proposed-signer84')))
# Call the actual committed Platform algorithm, rather than copy its projection.
sys.path.insert(0, str(NATIVE.parent))
spec = importlib.util.spec_from_file_location('rollout_native_projection', NATIVE)
native = importlib.util.module_from_spec(spec)
spec.loader.exec_module(native)
docs = [d for d in yaml.safe_load_all(ready) if d]
dep = next(d for d in docs if d['kind'] == 'Deployment')
stopped_dep = next(d for d in yaml.safe_load_all(stopped) if d and d['kind'] == 'Deployment')
projection = native.deployment_projection(dep)
assert projection == native.deployment_projection(stopped_dep)
c = dep['spec']['template']['spec']['containers'][0]
old = next(d for d in yaml.safe_load_all(retained) if d and d['kind'] == 'Deployment')
oldc = old['spec']['template']['spec']['containers'][0]
assert c['resources'] == oldc['resources']
for flag in ['--caller-audience', '--caller-auth-mode', '--caller-binding', '--caller-kubernetes-url', '--policy', '--registry']:
assert c['args'][c['args'].index(flag)+1] == oldc['args'][oldc['args'].index(flag)+1]
assert dep['spec']['strategy'] == {'type': 'Recreate'}
assert all(d['kind'] != 'Secret' for d in docs)
assert all(d['kind'] != 'Ingress' for d in docs)
other = lambda ds: [d for d in ds if d['kind'] not in ('Deployment', 'PersistentVolumeClaim')]
assert other(docs) == other([d for d in yaml.safe_load_all(retained) if d])
write_json('source-deployment-projection.json', projection)
write_json('render-evidence.json', {
'schema': 'access-engine.signer-rollout-source-render.v1',
'source_revision': REVISION, 'source_files': files,
'package_revision': 'd52f811ed5ec0be27c9c367e71d476125b3228be',
'package_baseline': 'extraction2b/current05a; unchanged, not this activation successor',
'image_state': 'verified-ordinary-ci-source-candidate' if receipt else 'pending; all-zero image is noncandidate shape sentinel',
'image_receipt_sha256': sha(args.image_receipt.read_bytes()) if receipt else None,
'source_rendered_projection_sha256': native.digest(native.encoded(projection)),
'deployment_projection_sha256': native.digest(native.encoded(projection)) if receipt else None,
'projection_algorithm_sha256': sha(NATIVE.read_bytes()),
'native_stopped_projection_verified': False,
'native_admission': False, 'execution_authorized': False,
'source_checks': {'caller_policy_registry_resources_unchanged': True,
'nondeployment_resources_unchanged_except_new_pvc': True,
'replicas0_and1_projection_equal': True, 'no_secret_generated': True},
'artifacts': {name: sha((HERE / name).read_bytes()) for name in [
'activation-values.proposed.yaml', 'retained-source-render.yaml', 'activation-source-render.yaml',
'stopped-source-render.yaml', 'source-render.diff', 'source-deployment-projection.json']},
})
if __name__ == '__main__':
main()

View file

@ -0,0 +1,42 @@
{
"schema": "access-engine.signer-rollout-source-render.v1",
"source_revision": "84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0",
"source_files": {
"charts/flex-auth/Chart.yaml": "sha256:e8e00d968a8ba2783f81312cebe27f0315e29f0b663033886d4104bab6a1b455",
"charts/flex-auth/templates/_helpers.tpl": "sha256:1e4a3ef486202b54d245060d2ee357880ee5b117732b2152bb4f75b489e0dc81",
"charts/flex-auth/templates/deployment.yaml": "sha256:b347614283e65d9e8006a93dfbdfec15360632f6c318761e245213460e158666",
"charts/flex-auth/templates/networkpolicy.yaml": "sha256:53d8ae1851b66c8c9f990fc56bc8ad9076741e2684f39470a7e1583b093d9c14",
"charts/flex-auth/templates/outbox-pvc.yaml": "sha256:284a78b0fd10537490f4f62e58c0cd85eb07b4222e932eb098c1bc5ddda8387d",
"charts/flex-auth/templates/rbac.yaml": "sha256:07033364ec9624f25e0a4ebaac9c29bc25edac93fde2c514fd946596e428e580",
"charts/flex-auth/templates/service.yaml": "sha256:9af858c02268d0492352ebe09efe7b6750711d48624a054b05e85707bf6a4e97",
"charts/flex-auth/templates/serviceaccount.yaml": "sha256:a62caa647b4a55f0092214c6a200bd3a6f34fa97d8989c55a387429a487bd6c8",
"charts/flex-auth/values.yaml": "sha256:0d5c3fa0c52d87ff6350d8f92527814b74bb8b7adadc92ded71856d9c83b1b03",
"examples/secrets-engine/policy_package.md": "sha256:657fa9312c9dcabee059ec31c89ee13396fe9aa54d852edc9e6559a74bbc139a",
"examples/secrets-engine/registry_snapshot.json": "sha256:37fba44ec0e0fd9b1e17a05ad1b7e4f9bb6ec9e0fc0bb3133e8af2ac6f832d7c",
"values/secrets-engine.yaml": "sha256:567189d14467815a77df5ad7e0f22e7184c8e5bb9f814cbc0fbb99102b3cfd55"
},
"package_revision": "d52f811ed5ec0be27c9c367e71d476125b3228be",
"package_baseline": "extraction2b/current05a; unchanged, not this activation successor",
"image_state": "verified-ordinary-ci-source-candidate",
"image_receipt_sha256": "sha256:af8e032d28776722ccf2bfe0f884577e568d8d0fb3713aa869e1d7d1c77c576f",
"source_rendered_projection_sha256": "sha256:8ce375f065552c551538f3acc7300c427407057eeff1d0b43755bfdf8a46169c",
"deployment_projection_sha256": "sha256:8ce375f065552c551538f3acc7300c427407057eeff1d0b43755bfdf8a46169c",
"projection_algorithm_sha256": "sha256:5a3b3b33bcad45ef2939413bfd3db76e5ee70e3b815d0d64af53147851aa3a0d",
"native_stopped_projection_verified": false,
"native_admission": false,
"execution_authorized": false,
"source_checks": {
"caller_policy_registry_resources_unchanged": true,
"nondeployment_resources_unchanged_except_new_pvc": true,
"replicas0_and1_projection_equal": true,
"no_secret_generated": true
},
"artifacts": {
"activation-values.proposed.yaml": "sha256:e76a30967add800ed8b588368757e7a5fa0ce4d8305ef658b5a4a08e377eb5b8",
"retained-source-render.yaml": "sha256:f979f7d05d5b0a54e3b8ac7c413aa4d401139f991b43412cdbde3d63249b0dd9",
"activation-source-render.yaml": "sha256:b7edecb8ec50a6d2db7623a3d962dbece46de81d3809d5f73b3aca20ff76b8c7",
"stopped-source-render.yaml": "sha256:be1c431b07477f4d19baf24874a23556c594328b2bb2b5f52af8ccc483d44133",
"source-render.diff": "sha256:77480a093011aba0e75afc49bc32c6d9ec4b4a9131d87b201f0d5cea9fc15888",
"source-deployment-projection.json": "sha256:056b6bf842713fb9a328dae901401d6867da47da68bfb8a4904bb0d7b8d9e912"
}
}

View file

@ -0,0 +1,191 @@
---
# Source: flex-auth/templates/networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: flex-auth-secrets-engine
policyTypes:
- Ingress
- Egress
egress:
- ports:
- port: 443
protocol: TCP
- port: 6443
protocol: TCP
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: secrets-engine
podSelector:
matchLabels:
app.kubernetes.io/name: secrets-engine
ports:
- port: 8080
protocol: TCP
---
# Source: flex-auth/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
automountServiceAccountToken: false
---
# Source: flex-auth/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flex-auth-secrets-engine-tokenreviewer
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
rules:
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
---
# Source: flex-auth/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flex-auth-secrets-engine-tokenreviewer
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flex-auth-secrets-engine-tokenreviewer
subjects:
- kind: ServiceAccount
name: flex-auth-secrets-engine
namespace: flex-auth
---
# Source: flex-auth/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
selector:
app.kubernetes.io/name: flex-auth-secrets-engine
ports:
- name: http
port: 8080
targetPort: http
---
# Source: flex-auth/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: flex-auth-secrets-engine
template:
metadata:
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
automountServiceAccountToken: false
serviceAccountName: flex-auth-secrets-engine
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: flex-auth
image: forgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd
imagePullPolicy: IfNotPresent
args:
- serve
- --addr
- 0.0.0.0:8080
- --registry
- /opt/flex-auth/examples/secrets-engine/registry_snapshot.json
- --policy
- /opt/flex-auth/examples/secrets-engine/policy_package.md
- --caller-audience
- "flex-auth"
- --caller-auth-mode
- "enforce"
- --caller-kubernetes-url
- "https://10.43.0.1"
- --caller-binding
- "secrets-engine=system:serviceaccount:secrets-engine:secrets-engine"
ports:
- name: http
containerPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 20
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 5
resources:
limits:
cpu: 300m
memory: 192Mi
requests:
cpu: 25m
memory: 32Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- mountPath: /var/run/secrets/flex-auth-reviewer
name: flex-auth-reviewer
readOnly: true
volumes:
- name: flex-auth-reviewer
projected:
defaultMode: 0440
sources:
- serviceAccountToken:
expirationSeconds: 3600
path: token
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt

View file

@ -0,0 +1,102 @@
{
"service_account": "flex-auth-secrets-engine",
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f",
"args": [
"serve",
"--addr",
"0.0.0.0:8080",
"--registry",
"/opt/flex-auth/examples/secrets-engine/registry_snapshot.json",
"--policy",
"/opt/flex-auth/examples/secrets-engine/policy_package.md",
"--envelope-signing-key-file",
"/var/run/secrets/decision-envelope-signing/seed",
"--envelope-signing-kid",
"flex-auth-lifecycle-20261016-r1",
"--outbox-max-bytes",
"67108864",
"--outbox-dir",
"/var/lib/flex-auth/outbox",
"--emission-source",
"flex-auth.secrets-engine",
"--caller-audience",
"flex-auth",
"--caller-auth-mode",
"enforce",
"--caller-kubernetes-url",
"https://10.43.0.1",
"--caller-binding",
"secrets-engine=system:serviceaccount:secrets-engine:secrets-engine"
],
"volume_mounts": [
{
"mountPath": "/var/run/secrets/decision-envelope-signing",
"name": "flex-auth-envelope-signing",
"readOnly": true
},
{
"mountPath": "/var/lib/flex-auth/outbox",
"name": "flex-auth-outbox"
},
{
"mountPath": "/var/run/secrets/flex-auth-reviewer",
"name": "flex-auth-reviewer",
"readOnly": true
}
],
"volumes": [
{
"name": "flex-auth-envelope-signing",
"secret": {
"secretName": "flex-auth-secrets-engine-envelope-signing",
"defaultMode": 288,
"items": [
{
"key": "seed",
"path": "seed"
}
]
}
},
{
"name": "flex-auth-outbox",
"persistentVolumeClaim": {
"claimName": "flex-auth-secrets-engine-outbox"
}
},
{
"name": "flex-auth-reviewer",
"projected": {
"defaultMode": 288,
"sources": [
{
"serviceAccountToken": {
"expirationSeconds": 3600,
"path": "token"
}
},
{
"configMap": {
"name": "kube-root-ca.crt",
"items": [
{
"key": "ca.crt",
"path": "ca.crt"
}
]
}
}
]
}
}
],
"security_context": {
"runAsNonRoot": true,
"runAsUser": 65532,
"runAsGroup": 65532,
"fsGroup": 65532,
"seccompProfile": {
"type": "RuntimeDefault"
}
}
}

View file

@ -0,0 +1,89 @@
--- retained84
+++ proposed-signer84
@@ -44,6 +44,25 @@
app.kubernetes.io/part-of: flex-auth
automountServiceAccountToken: false
---
+# Source: flex-auth/templates/outbox-pvc.yaml
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: flex-auth-secrets-engine-outbox
+ annotations:
+ helm.sh/resource-policy: keep
+ labels:
+ app.kubernetes.io/name: flex-auth-secrets-engine
+ app.kubernetes.io/instance: flex-auth-secrets-engine
+ app.kubernetes.io/part-of: flex-auth
+spec:
+ accessModes:
+ - ReadWriteOnce
+ storageClassName: "local-path"
+ resources:
+ requests:
+ storage: "1Gi"
+---
# Source: flex-auth/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
@@ -106,6 +125,8 @@
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
+ strategy:
+ type: Recreate
replicas: 1
selector:
matchLabels:
@@ -128,7 +149,7 @@
type: RuntimeDefault
containers:
- name: flex-auth
- image: forgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd
+ image: forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f
imagePullPolicy: IfNotPresent
args:
- serve
@@ -138,6 +159,16 @@
- /opt/flex-auth/examples/secrets-engine/registry_snapshot.json
- --policy
- /opt/flex-auth/examples/secrets-engine/policy_package.md
+ - --envelope-signing-key-file
+ - "/var/run/secrets/decision-envelope-signing/seed"
+ - --envelope-signing-kid
+ - "flex-auth-lifecycle-20261016-r1"
+ - --outbox-max-bytes
+ - "67108864"
+ - --outbox-dir
+ - "/var/lib/flex-auth/outbox"
+ - --emission-source
+ - "flex-auth.secrets-engine"
- --caller-audience
- "flex-auth"
- --caller-auth-mode
@@ -173,10 +204,25 @@
- ALL
readOnlyRootFilesystem: true
volumeMounts:
+ - mountPath: "/var/run/secrets/decision-envelope-signing"
+ name: flex-auth-envelope-signing
+ readOnly: true
+ - mountPath: "/var/lib/flex-auth/outbox"
+ name: flex-auth-outbox
- mountPath: /var/run/secrets/flex-auth-reviewer
name: flex-auth-reviewer
readOnly: true
volumes:
+ - name: flex-auth-envelope-signing
+ secret:
+ secretName: "flex-auth-secrets-engine-envelope-signing"
+ defaultMode: 0440
+ items:
+ - key: "seed"
+ path: seed
+ - name: flex-auth-outbox
+ persistentVolumeClaim:
+ claimName: "flex-auth-secrets-engine-outbox"
- name: flex-auth-reviewer
projected:
defaultMode: 0440

View file

@ -0,0 +1,229 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: flex-auth-secrets-engine
policyTypes:
- Ingress
- Egress
egress:
- ports:
- port: 443
protocol: TCP
- port: 6443
protocol: TCP
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: secrets-engine
podSelector:
matchLabels:
app.kubernetes.io/name: secrets-engine
ports:
- port: 8080
protocol: TCP
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
automountServiceAccountToken: false
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: flex-auth-secrets-engine-outbox
annotations:
helm.sh/resource-policy: keep
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flex-auth-secrets-engine-tokenreviewer
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
rules:
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flex-auth-secrets-engine-tokenreviewer
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flex-auth-secrets-engine-tokenreviewer
subjects:
- kind: ServiceAccount
name: flex-auth-secrets-engine
namespace: flex-auth
---
apiVersion: v1
kind: Service
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
selector:
app.kubernetes.io/name: flex-auth-secrets-engine
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
strategy:
type: Recreate
replicas: 0
selector:
matchLabels:
app.kubernetes.io/name: flex-auth-secrets-engine
template:
metadata:
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
automountServiceAccountToken: false
serviceAccountName: flex-auth-secrets-engine
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: flex-auth
image: forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f
imagePullPolicy: IfNotPresent
args:
- serve
- --addr
- 0.0.0.0:8080
- --registry
- /opt/flex-auth/examples/secrets-engine/registry_snapshot.json
- --policy
- /opt/flex-auth/examples/secrets-engine/policy_package.md
- --envelope-signing-key-file
- /var/run/secrets/decision-envelope-signing/seed
- --envelope-signing-kid
- flex-auth-lifecycle-20261016-r1
- --outbox-max-bytes
- '67108864'
- --outbox-dir
- /var/lib/flex-auth/outbox
- --emission-source
- flex-auth.secrets-engine
- --caller-audience
- flex-auth
- --caller-auth-mode
- enforce
- --caller-kubernetes-url
- https://10.43.0.1
- --caller-binding
- secrets-engine=system:serviceaccount:secrets-engine:secrets-engine
ports:
- name: http
containerPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 20
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 5
resources:
limits:
cpu: 300m
memory: 192Mi
requests:
cpu: 25m
memory: 32Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- mountPath: /var/run/secrets/decision-envelope-signing
name: flex-auth-envelope-signing
readOnly: true
- mountPath: /var/lib/flex-auth/outbox
name: flex-auth-outbox
- mountPath: /var/run/secrets/flex-auth-reviewer
name: flex-auth-reviewer
readOnly: true
volumes:
- name: flex-auth-envelope-signing
secret:
secretName: flex-auth-secrets-engine-envelope-signing
defaultMode: 288
items:
- key: seed
path: seed
- name: flex-auth-outbox
persistentVolumeClaim:
claimName: flex-auth-secrets-engine-outbox
- name: flex-auth-reviewer
projected:
defaultMode: 288
sources:
- serviceAccountToken:
expirationSeconds: 3600
path: token
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt

View file

@ -0,0 +1,104 @@
{
"observed_at": "2026-10-16T15:20:06.611593+00:00",
"scope": "metadata-only; no credentials, Secret objects, logs, exec or writes",
"queries": {
"nodes": {
"command": [
"kubectl",
"get",
"nodes",
"-o",
"jsonpath={range .items[*]}{.metadata.name}{\" \"}{.status.addresses}{\" \"}{.status.allocatable}{\"\\n\"}{end}"
],
"exit_code": 0,
"output": "239.62.205.92.host.secureserver.net [{\"address\":\"92.205.62.239\",\"type\":\"InternalIP\"},{\"address\":\"2a00:1169:111:6d70::\",\"type\":\"InternalIP\"},{\"address\":\"239.62.205.92.host.secureserver.net\",\"type\":\"Hostname\"}] {\"cpu\":\"4\",\"ephemeral-storage\":\"196555267123\",\"hugepages-1Gi\":\"0\",\"hugepages-2Mi\":\"0\",\"memory\":\"16377032Ki\",\"pods\":\"110\"}\n",
"stderr_present": false
},
"deployment": {
"command": [
"kubectl",
"-n",
"flex-auth",
"get",
"deployment",
"flex-auth-secrets-engine",
"-o",
"jsonpath={.metadata.name}{\"\\n\"}{.metadata.uid}{\"\\n\"}{.metadata.labels.helm\\.sh/chart}{\"\\n\"}{.metadata.generation}{\" \"}{.status.observedGeneration}{\"\\n\"}{.spec.replicas}{\" \"}{.spec.strategy}{\"\\n\"}{.spec.template.spec.serviceAccountName}{\"\\n\"}{.spec.template.spec.containers[0].image}{\"\\n\"}{.spec.template.spec.containers[0].args}{\"\\n\"}{.spec.template.spec.containers[0].volumeMounts}{\"\\n\"}{.spec.template.spec.volumes}{\"\\n\"}{.spec.template.spec.securityContext}{\"\\n\"}{.spec.template.spec.containers[0].resources}{\"\\n\"}{.status.availableReplicas}{\"\\n\"}"
],
"exit_code": 0,
"output": "flex-auth-secrets-engine\nf83a5f5c-7dd7-4aac-bc02-d447e423e6ae\n\n4 4\n1 {\"rollingUpdate\":{\"maxSurge\":\"25%\",\"maxUnavailable\":\"25%\"},\"type\":\"RollingUpdate\"}\nflex-auth-secrets-engine\nforgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd\n[\"serve\",\"--addr\",\"0.0.0.0:8080\",\"--registry\",\"/opt/flex-auth/examples/secrets-engine/registry_snapshot.json\",\"--policy\",\"/opt/flex-auth/examples/secrets-engine/policy_package.md\",\"--caller-auth-mode\",\"enforce\",\"--caller-kubernetes-url\",\"https://10.43.0.1\",\"--caller-binding\",\"secrets-engine=system:serviceaccount:secrets-engine:secrets-engine\"]\n[{\"mountPath\":\"/var/run/secrets/flex-auth-reviewer\",\"name\":\"flex-auth-reviewer\",\"readOnly\":true}]\n[{\"name\":\"flex-auth-reviewer\",\"projected\":{\"defaultMode\":288,\"sources\":[{\"serviceAccountToken\":{\"expirationSeconds\":3600,\"path\":\"token\"}},{\"configMap\":{\"items\":[{\"key\":\"ca.crt\",\"path\":\"ca.crt\"}],\"name\":\"kube-root-ca.crt\"}}]}}]\n{\"fsGroup\":65532,\"runAsGroup\":65532,\"runAsNonRoot\":true,\"runAsUser\":65532,\"seccompProfile\":{\"type\":\"RuntimeDefault\"}}\n{\"limits\":{\"cpu\":\"300m\",\"memory\":\"192Mi\"},\"requests\":{\"cpu\":\"25m\",\"memory\":\"32Mi\"}}\n1\n",
"stderr_present": false
},
"pods": {
"command": [
"kubectl",
"-n",
"flex-auth",
"get",
"pods",
"-l",
"app.kubernetes.io/name=flex-auth-secrets-engine",
"-o",
"jsonpath={range .items[*]}{.metadata.name}{\" \"}{.metadata.uid}{\" \"}{.status.phase}{\" \"}{.spec.nodeName}{\" \"}{.status.containerStatuses[0].imageID}{\"\\n\"}{end}"
],
"exit_code": 0,
"output": "flex-auth-secrets-engine-6467b96c6f-ncl5t f9598eac-6db2-41a5-8054-fb35bb5c0861 Running 239.62.205.92.host.secureserver.net forgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd\n",
"stderr_present": false
},
"service": {
"command": [
"kubectl",
"-n",
"flex-auth",
"get",
"service",
"flex-auth-secrets-engine",
"-o",
"jsonpath={.metadata.name}{\" \"}{.spec.clusterIP}{\" \"}{.spec.selector}{\" \"}{.spec.ports}{\"\\n\"}"
],
"exit_code": 0,
"output": "flex-auth-secrets-engine 10.43.40.221 {\"app.kubernetes.io/name\":\"flex-auth-secrets-engine\"} [{\"name\":\"http\",\"port\":8080,\"protocol\":\"TCP\",\"targetPort\":\"http\"}]\n",
"stderr_present": false
},
"pvcs": {
"command": [
"kubectl",
"-n",
"flex-auth",
"get",
"pvc",
"-o",
"jsonpath={range .items[*]}{.metadata.name}{\" \"}{.spec.accessModes}{\" \"}{.spec.storageClassName}{\" \"}{.spec.volumeName}{\" \"}{.spec.resources.requests.storage}{\" \"}{.status.capacity}{\" \"}{.status.phase}{\"\\n\"}{end}"
],
"exit_code": 0,
"output": "",
"stderr_present": false
},
"storage_classes": {
"command": [
"kubectl",
"get",
"storageclass",
"-o",
"jsonpath={range .items[*]}{.metadata.name}{\" \"}{.provisioner}{\" \"}{.volumeBindingMode}{\" \"}{.reclaimPolicy}{\" \"}{.allowVolumeExpansion}{\"\\n\"}{end}"
],
"exit_code": 0,
"output": "local-path rancher.io/local-path WaitForFirstConsumer Delete \n",
"stderr_present": false
},
"namespace_serviceaccounts": {
"command": [
"kubectl",
"-n",
"flex-auth",
"get",
"serviceaccount",
"-o",
"jsonpath={range .items[*]}{.metadata.name}{\" \"}{.automountServiceAccountToken}{\"\\n\"}{end}"
],
"exit_code": 0,
"output": "default \nflex-auth-application-readback false\nflex-auth-informed-decision-lifecycle false\nflex-auth-informed-decision-sitting false\nflex-auth-informed-decision-t03 false\nflex-auth-ops-warden false\nflex-auth-ops-warden-fetch false\nflex-auth-secrets-engine false\nflex-auth-tenant-engine false\nflex-auth-user-engine false\n",
"stderr_present": false
}
}
}

View file

@ -0,0 +1,15 @@
# Stopped outbox Helm state: source proposal R1
Access owner preparation for existing FLEX-WP-0024-T06. This isolated proposal changes both chart guards to accept numeric replicaCount exactly0 or1 when outbox is enabled. It rejects fractions, negative numbers,2+, booleans, strings, null and collections. Recreate, RWO, the process lock, bounded outbox and default one replica remain unchanged. No canonical owner files, frozen rollout R1 or Platform P2 files were changed.
Allowing zero writers is consistent with the single-writer invariant and the required Platform custody fence. It lets the ordinary owner Helm path review/install the exact signer template with replicaCount0, then independently prove that all signer pods are gone before bootstrap. It removes the need for a separate derived stopped manifest or imperative scale/Deployment patch. Zero desired replicas alone proves neither current pod absence nor a held exclusive maintenance window; competing controllers, consumers and all native admission gates remain separately reviewed.
Helm decodes YAML numeric0 and0.0 identically as float64/text0, likewise1 and1.0. The guard therefore checks mathematical integer value0/1, not a lexical spelling it cannot recover. YAML/JSON integral floats0.0/1.0 are accepted. CLI `--set replicaCount=0.0` yields a string and is rejected, as are explicit `--set-string` numeric strings. The prior coercing `int` check could admit some fractional/string forms as1; the new guard removes that truncation/coercion route.
Validation: 86 actual Helm renders cover both charts via YAML, JSON and CLI, stopped/ready object equality after changing only replicas, unsafe value refusals and explicit integral numeric semantics. Existing `tests/test_outbox_chart.py` passes, including capacity, delivery and receiver-port boundaries. Patch applies cleanly to canonical84 source in read-only `git apply --check`. The unchanged baseline refused the first stopped render; the isolated patch passes it. No native API was contacted.
`stopped-source-render.yaml` and `ready-source-render.yaml` were genuinely rendered by Helm against patched current chart source plus frozen rollout R1 values, with explicit replicaCount0/1 respectively. The Platform native projection algorithm gives both the same source-only digest `sha256:8ce375f065552c551538f3acc7300c427407057eeff1d0b43755bfdf8a46169c`, equal to frozen R1. The frozen84 image remains `sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f`; this patch changes no binary, Containerfile, policy or registry. A separately reviewed source/chart successor pin is still required: the old84 chart hashes do not authenticate these changed templates. A source successor may reuse that independently verified immutable84 image by explicit reviewed chart/image selection; it cannot claim the image revision label changed to the new chart revision. No rebuilt image is needed to express a chart-only stopped state.
If adopted, freeze a new rollout revision and update package/scope exact source hashes while preserving original R1/P2. Equal runtime projection does not authenticate new chart source or automatically renew approvals. Actual stopped native projection equality, image/runtime verification, ordinary Helm history, controllers, PVC/Retain, custody and all independent dispositions remain open.
Isolated source and tests: `/tmp/access-stopped-chart-proposal-1gom08nf`. Apply `implementation.patch` only through the existing owner source integration path after review. No rollout or privileged action is authorized.

View file

@ -0,0 +1,98 @@
--- a/charts/flex-auth/templates/deployment.yaml
+++ b/charts/flex-auth/templates/deployment.yaml
@@ -28,8 +28,10 @@
{{- include "flex-auth.labels" . | nindent 4 }}
spec:
{{- if .Values.outbox.enabled }}
- {{- if ne (int .Values.replicaCount) 1 }}
- {{- fail "outbox requires replicaCount=1: one writer per source and PVC" }}
+ {{- $replicas := .Values.replicaCount }}
+ {{- $numericKind := has (kindOf $replicas) (list "int" "int64" "int32" "uint" "uint64" "float64" "float32") }}
+ {{- if or (not $numericKind) (not (or (eq (toString $replicas) "0") (eq (toString $replicas) "1"))) }}
+ {{- fail "outbox requires numeric integer replicaCount 0 (stopped) or 1 (single writer)" }}
{{- end }}
strategy:
type: Recreate
--- a/charts/access-engine/templates/deployment.yaml
+++ b/charts/access-engine/templates/deployment.yaml
@@ -29,8 +29,10 @@
{{- include "access-engine.labels" . | nindent 4 }}
spec:
{{- if .Values.outbox.enabled }}
- {{- if ne (int .Values.replicaCount) 1 }}
- {{- fail "outbox requires replicaCount=1: one writer per source and PVC" }}
+ {{- $replicas := .Values.replicaCount }}
+ {{- $numericKind := has (kindOf $replicas) (list "int" "int64" "int32" "uint" "uint64" "float64" "float32") }}
+ {{- if or (not $numericKind) (not (or (eq (toString $replicas) "0") (eq (toString $replicas) "1"))) }}
+ {{- fail "outbox requires numeric integer replicaCount 0 (stopped) or 1 (single writer)" }}
{{- end }}
strategy:
type: Recreate
--- /dev/null
+++ b/tests/test_outbox_replicas_chart.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+"""Actual Helm stop/start renders and unsafe replica value rejection; offline only."""
+import copy
+import json
+from pathlib import Path
+import subprocess
+import tempfile
+import yaml
+
+ROOT = Path(__file__).resolve().parents[1]
+
+def render(chart, value, encoding="yaml", success=True):
+ with tempfile.TemporaryDirectory() as directory:
+ command = ["helm", "template", "flex-auth-secrets-engine", str(ROOT / "charts" / chart),
+ "--namespace", "flex-auth", "-f", str(ROOT / "values/secrets-engine.yaml"),
+ "--set", "outbox.enabled=true", "--set", "outbox.source=flex-auth.secrets-engine",
+ "--set", "envelopeSigning.enabled=true", "--set", "envelopeSigning.kid=test-kid",
+ "--set", "envelopeSigning.secretName=proposed-seed", "--set", "envelopeSigning.secretKey=seed"]
+ if encoding == "yaml":
+ overlay = Path(directory) / "values.yaml"
+ overlay.write_text(yaml.safe_dump({"replicaCount": value}))
+ command += ["-f", str(overlay)]
+ elif encoding == "json":
+ command += ["--set-json", "replicaCount=" + json.dumps(value)]
+ elif encoding == "string":
+ command += ["--set-string", "replicaCount=" + str(value)]
+ else:
+ command += ["--set", "replicaCount=" + str(value)]
+ result = subprocess.run(command, capture_output=True, text=True)
+ if not success:
+ assert result.returncode != 0, (chart, encoding, value)
+ assert "replicaCount" in result.stderr, result.stderr
+ return
+ assert result.returncode == 0, result.stderr
+ documents = [d for d in yaml.safe_load_all(result.stdout) if d]
+ deployment = next(d for d in documents if d["kind"] == "Deployment")
+ assert deployment["spec"]["strategy"] == {"type": "Recreate"}
+ assert type(deployment["spec"]["replicas"]) is int
+ assert deployment["spec"]["replicas"] == value
+ return documents
+
+count = 0
+for chart in ("flex-auth", "access-engine"):
+ for encoding in ("yaml", "json", "cli"):
+ stopped = render(chart, 0, encoding)
+ ready = render(chart, 1, encoding)
+ normalized = copy.deepcopy(ready)
+ next(d for d in normalized if d["kind"] == "Deployment")["spec"]["replicas"] = 0
+ assert stopped == normalized, "stop must change replicas only, including identical pod projection/PVC"
+ count += 2
+ for encoding in ("yaml", "json"):
+ for value in (-1, 2, 1.5, 0.5, True, False, None, "0", "1", [], {}):
+ render(chart, value, encoding, success=False)
+ count += 1
+ # Helm loses lexical 0 vs0.0 and1 vs1.0 before template evaluation.
+ for value in (0.0, 1.0):
+ render(chart, value, encoding)
+ count += 1
+ for value in ("-1", "2", "0.5", "1.5", "true", "false", "null", "0.0", "1.0"):
+ render(chart, value, "cli", success=False)
+ count += 1
+ for value in ("0", "1"):
+ render(chart, value, "string", success=False)
+ count += 1
+print(f"stopped/single-writer chart checks passed: {count} actual Helm renders")

View file

@ -0,0 +1,15 @@
{
"schema": "access-engine.stopped-chart-source-proposal.v1",
"proposal_id": "FLEX24-STOPPED-CHART-20261016-R1",
"base_revision": "84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0",
"disposition": "approve-source-proposal-for-independent-review-only",
"execution_authorized": false,
"files": {
"README.md": "sha256:00bb92f649f01c75731418d9e3f2113a71a7ef5c0e6c112db18c34dd9aa6255c",
"implementation.patch": "sha256:4baf8f35028020a5718ffc86d57827cf8d48fb736211ae725964ac62b28086ff",
"projection-evidence.json": "sha256:49d210a08432699cab9c9f718fdb250c6f0f3de582acdd2300946944c7cfef23",
"ready-source-render.yaml": "sha256:b7edecb8ec50a6d2db7623a3d962dbece46de81d3809d5f73b3aca20ff76b8c7",
"stopped-source-render.yaml": "sha256:03b631f0fe17c63494c14619199f69f2a2a9ab245d00f1ec441248868b24c071",
"validation.json": "sha256:7540335f36593d9f8eb432901e1d1a04684a80544bb8bb240b561ed2a5de608c"
}
}

View file

@ -0,0 +1,8 @@
{
"source_revision": "84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0 plus isolated implementation.patch",
"source_rendered_projection_sha256": "sha256:8ce375f065552c551538f3acc7300c427407057eeff1d0b43755bfdf8a46169c",
"stopped_equals_ready": true,
"unchanged_from_frozen_R1": true,
"native_stopped_fence_verified": false,
"execution_authorized": false
}

View file

@ -0,0 +1,237 @@
---
# Source: flex-auth/templates/networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: flex-auth-secrets-engine
policyTypes:
- Ingress
- Egress
egress:
- ports:
- port: 443
protocol: TCP
- port: 6443
protocol: TCP
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: secrets-engine
podSelector:
matchLabels:
app.kubernetes.io/name: secrets-engine
ports:
- port: 8080
protocol: TCP
---
# Source: flex-auth/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
automountServiceAccountToken: false
---
# Source: flex-auth/templates/outbox-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: flex-auth-secrets-engine-outbox
annotations:
helm.sh/resource-policy: keep
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
accessModes:
- ReadWriteOnce
storageClassName: "local-path"
resources:
requests:
storage: "1Gi"
---
# Source: flex-auth/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flex-auth-secrets-engine-tokenreviewer
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
rules:
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
---
# Source: flex-auth/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flex-auth-secrets-engine-tokenreviewer
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flex-auth-secrets-engine-tokenreviewer
subjects:
- kind: ServiceAccount
name: flex-auth-secrets-engine
namespace: flex-auth
---
# Source: flex-auth/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
selector:
app.kubernetes.io/name: flex-auth-secrets-engine
ports:
- name: http
port: 8080
targetPort: http
---
# Source: flex-auth/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
strategy:
type: Recreate
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: flex-auth-secrets-engine
template:
metadata:
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
automountServiceAccountToken: false
serviceAccountName: flex-auth-secrets-engine
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: flex-auth
image: forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f
imagePullPolicy: IfNotPresent
args:
- serve
- --addr
- 0.0.0.0:8080
- --registry
- /opt/flex-auth/examples/secrets-engine/registry_snapshot.json
- --policy
- /opt/flex-auth/examples/secrets-engine/policy_package.md
- --envelope-signing-key-file
- "/var/run/secrets/decision-envelope-signing/seed"
- --envelope-signing-kid
- "flex-auth-lifecycle-20261016-r1"
- --outbox-max-bytes
- "67108864"
- --outbox-dir
- "/var/lib/flex-auth/outbox"
- --emission-source
- "flex-auth.secrets-engine"
- --caller-audience
- "flex-auth"
- --caller-auth-mode
- "enforce"
- --caller-kubernetes-url
- "https://10.43.0.1"
- --caller-binding
- "secrets-engine=system:serviceaccount:secrets-engine:secrets-engine"
ports:
- name: http
containerPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 20
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 5
resources:
limits:
cpu: 300m
memory: 192Mi
requests:
cpu: 25m
memory: 32Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- mountPath: "/var/run/secrets/decision-envelope-signing"
name: flex-auth-envelope-signing
readOnly: true
- mountPath: "/var/lib/flex-auth/outbox"
name: flex-auth-outbox
- mountPath: /var/run/secrets/flex-auth-reviewer
name: flex-auth-reviewer
readOnly: true
volumes:
- name: flex-auth-envelope-signing
secret:
secretName: "flex-auth-secrets-engine-envelope-signing"
defaultMode: 0440
items:
- key: "seed"
path: seed
- name: flex-auth-outbox
persistentVolumeClaim:
claimName: "flex-auth-secrets-engine-outbox"
- name: flex-auth-reviewer
projected:
defaultMode: 0440
sources:
- serviceAccountToken:
expirationSeconds: 3600
path: token
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt

View file

@ -0,0 +1,237 @@
---
# Source: flex-auth/templates/networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: flex-auth-secrets-engine
policyTypes:
- Ingress
- Egress
egress:
- ports:
- port: 443
protocol: TCP
- port: 6443
protocol: TCP
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: secrets-engine
podSelector:
matchLabels:
app.kubernetes.io/name: secrets-engine
ports:
- port: 8080
protocol: TCP
---
# Source: flex-auth/templates/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
automountServiceAccountToken: false
---
# Source: flex-auth/templates/outbox-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: flex-auth-secrets-engine-outbox
annotations:
helm.sh/resource-policy: keep
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
accessModes:
- ReadWriteOnce
storageClassName: "local-path"
resources:
requests:
storage: "1Gi"
---
# Source: flex-auth/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: flex-auth-secrets-engine-tokenreviewer
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
rules:
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
---
# Source: flex-auth/templates/rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: flex-auth-secrets-engine-tokenreviewer
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: flex-auth-secrets-engine-tokenreviewer
subjects:
- kind: ServiceAccount
name: flex-auth-secrets-engine
namespace: flex-auth
---
# Source: flex-auth/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
selector:
app.kubernetes.io/name: flex-auth-secrets-engine
ports:
- name: http
port: 8080
targetPort: http
---
# Source: flex-auth/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: flex-auth-secrets-engine
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
strategy:
type: Recreate
replicas: 0
selector:
matchLabels:
app.kubernetes.io/name: flex-auth-secrets-engine
template:
metadata:
labels:
app.kubernetes.io/name: flex-auth-secrets-engine
app.kubernetes.io/instance: flex-auth-secrets-engine
app.kubernetes.io/part-of: flex-auth
spec:
automountServiceAccountToken: false
serviceAccountName: flex-auth-secrets-engine
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: flex-auth
image: forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f
imagePullPolicy: IfNotPresent
args:
- serve
- --addr
- 0.0.0.0:8080
- --registry
- /opt/flex-auth/examples/secrets-engine/registry_snapshot.json
- --policy
- /opt/flex-auth/examples/secrets-engine/policy_package.md
- --envelope-signing-key-file
- "/var/run/secrets/decision-envelope-signing/seed"
- --envelope-signing-kid
- "flex-auth-lifecycle-20261016-r1"
- --outbox-max-bytes
- "67108864"
- --outbox-dir
- "/var/lib/flex-auth/outbox"
- --emission-source
- "flex-auth.secrets-engine"
- --caller-audience
- "flex-auth"
- --caller-auth-mode
- "enforce"
- --caller-kubernetes-url
- "https://10.43.0.1"
- --caller-binding
- "secrets-engine=system:serviceaccount:secrets-engine:secrets-engine"
ports:
- name: http
containerPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 20
readinessProbe:
httpGet:
path: /healthz
port: http
periodSeconds: 5
resources:
limits:
cpu: 300m
memory: 192Mi
requests:
cpu: 25m
memory: 32Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumeMounts:
- mountPath: "/var/run/secrets/decision-envelope-signing"
name: flex-auth-envelope-signing
readOnly: true
- mountPath: "/var/lib/flex-auth/outbox"
name: flex-auth-outbox
- mountPath: /var/run/secrets/flex-auth-reviewer
name: flex-auth-reviewer
readOnly: true
volumes:
- name: flex-auth-envelope-signing
secret:
secretName: "flex-auth-secrets-engine-envelope-signing"
defaultMode: 0440
items:
- key: "seed"
path: seed
- name: flex-auth-outbox
persistentVolumeClaim:
claimName: "flex-auth-secrets-engine-outbox"
- name: flex-auth-reviewer
projected:
defaultMode: 0440
sources:
- serviceAccountToken:
expirationSeconds: 3600
path: token
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt

View file

@ -0,0 +1,10 @@
{
"source_checks": "passed",
"actual_new_helm_renders": 86,
"existing_outbox_chart_suite": "passed: optional outbox, audit delivery and receiver-port checks",
"git_apply_check": "passed against canonical84",
"runtime_changes": false,
"canonical_owner_files_changed": false,
"frozen_rollout_R1_P2_changed": false,
"incremental_source_spend_eur": 0
}

View file

@ -0,0 +1,7 @@
# Stopped-chart source proposal R2
R2 preserves frozen R1 and adds only the bounded source note to existing FLEX-WP-0024-T06. No status, admission or authority field changes. `implementation-r2.patch` is the complete four-file proposal against canonical84; `r1-to-r2.patch` adds only the workplan note after R1. `postimages/` contains exact final four-file bytes, with SHA256s in manifest.json.
Both deployment guards and the 86-render test remain byte-identical to independently checked R1. Existing outbox/delivery/receiver-port suites and root independent reconstruction passed. The isolated source change is approved for source integration only; native approval and actual stopped-fence proof remain absent. Current immutable source84 image09a83b remains selected explicitly alongside a future new chart/source commit. This chart/test/workplan-only delta does not trigger the existing image workflow or require rebuilding the binary image; new source pinning cannot relabel the unchanged image.
See ../stopped-chart-r1/README.md for guard semantics, Helm integral numeric decoding and source-only projection correspondence. Neither R1, rollout R1, Platform P2, canonical owner files nor runtime was changed in this preparation. Coordinator must run ordinary owner consistency sync after integration of the existing workplan file.

View file

@ -0,0 +1,127 @@
--- a/charts/flex-auth/templates/deployment.yaml
+++ b/charts/flex-auth/templates/deployment.yaml
@@ -28,8 +28,10 @@
{{- include "flex-auth.labels" . | nindent 4 }}
spec:
{{- if .Values.outbox.enabled }}
- {{- if ne (int .Values.replicaCount) 1 }}
- {{- fail "outbox requires replicaCount=1: one writer per source and PVC" }}
+ {{- $replicas := .Values.replicaCount }}
+ {{- $numericKind := has (kindOf $replicas) (list "int" "int64" "int32" "uint" "uint64" "float64" "float32") }}
+ {{- if or (not $numericKind) (not (or (eq (toString $replicas) "0") (eq (toString $replicas) "1"))) }}
+ {{- fail "outbox requires numeric integer replicaCount 0 (stopped) or 1 (single writer)" }}
{{- end }}
strategy:
type: Recreate
--- a/charts/access-engine/templates/deployment.yaml
+++ b/charts/access-engine/templates/deployment.yaml
@@ -29,8 +29,10 @@
{{- include "access-engine.labels" . | nindent 4 }}
spec:
{{- if .Values.outbox.enabled }}
- {{- if ne (int .Values.replicaCount) 1 }}
- {{- fail "outbox requires replicaCount=1: one writer per source and PVC" }}
+ {{- $replicas := .Values.replicaCount }}
+ {{- $numericKind := has (kindOf $replicas) (list "int" "int64" "int32" "uint" "uint64" "float64" "float32") }}
+ {{- if or (not $numericKind) (not (or (eq (toString $replicas) "0") (eq (toString $replicas) "1"))) }}
+ {{- fail "outbox requires numeric integer replicaCount 0 (stopped) or 1 (single writer)" }}
{{- end }}
strategy:
type: Recreate
--- /dev/null
+++ b/tests/test_outbox_replicas_chart.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+"""Actual Helm stop/start renders and unsafe replica value rejection; offline only."""
+import copy
+import json
+from pathlib import Path
+import subprocess
+import tempfile
+import yaml
+
+ROOT = Path(__file__).resolve().parents[1]
+
+def render(chart, value, encoding="yaml", success=True):
+ with tempfile.TemporaryDirectory() as directory:
+ command = ["helm", "template", "flex-auth-secrets-engine", str(ROOT / "charts" / chart),
+ "--namespace", "flex-auth", "-f", str(ROOT / "values/secrets-engine.yaml"),
+ "--set", "outbox.enabled=true", "--set", "outbox.source=flex-auth.secrets-engine",
+ "--set", "envelopeSigning.enabled=true", "--set", "envelopeSigning.kid=test-kid",
+ "--set", "envelopeSigning.secretName=proposed-seed", "--set", "envelopeSigning.secretKey=seed"]
+ if encoding == "yaml":
+ overlay = Path(directory) / "values.yaml"
+ overlay.write_text(yaml.safe_dump({"replicaCount": value}))
+ command += ["-f", str(overlay)]
+ elif encoding == "json":
+ command += ["--set-json", "replicaCount=" + json.dumps(value)]
+ elif encoding == "string":
+ command += ["--set-string", "replicaCount=" + str(value)]
+ else:
+ command += ["--set", "replicaCount=" + str(value)]
+ result = subprocess.run(command, capture_output=True, text=True)
+ if not success:
+ assert result.returncode != 0, (chart, encoding, value)
+ assert "replicaCount" in result.stderr, result.stderr
+ return
+ assert result.returncode == 0, result.stderr
+ documents = [d for d in yaml.safe_load_all(result.stdout) if d]
+ deployment = next(d for d in documents if d["kind"] == "Deployment")
+ assert deployment["spec"]["strategy"] == {"type": "Recreate"}
+ assert type(deployment["spec"]["replicas"]) is int
+ assert deployment["spec"]["replicas"] == value
+ return documents
+
+count = 0
+for chart in ("flex-auth", "access-engine"):
+ for encoding in ("yaml", "json", "cli"):
+ stopped = render(chart, 0, encoding)
+ ready = render(chart, 1, encoding)
+ normalized = copy.deepcopy(ready)
+ next(d for d in normalized if d["kind"] == "Deployment")["spec"]["replicas"] = 0
+ assert stopped == normalized, "stop must change replicas only, including identical pod projection/PVC"
+ count += 2
+ for encoding in ("yaml", "json"):
+ for value in (-1, 2, 1.5, 0.5, True, False, None, "0", "1", [], {}):
+ render(chart, value, encoding, success=False)
+ count += 1
+ # Helm loses lexical 0 vs0.0 and1 vs1.0 before template evaluation.
+ for value in (0.0, 1.0):
+ render(chart, value, encoding)
+ count += 1
+ for value in ("-1", "2", "0.5", "1.5", "true", "false", "null", "0.0", "1.0"):
+ render(chart, value, "cli", success=False)
+ count += 1
+ for value in ("0", "1"):
+ render(chart, value, "string", success=False)
+ count += 1
+print(f"stopped/single-writer chart checks passed: {count} actual Helm renders")
--- a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
+++ b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
@@ -302,3 +302,26 @@
reef binding, custody, native rollout or consumer trust admission. Future signer
image/config adoption remains separate. T06 remains `progress`; no second work
item duplicates this bounded dependency.
+
+
+2026-10-16 bounded stopped-chart source preparation (Custodian
+`access-native-admission/stopped-chart-r2`): both outbox charts admit numeric
+replicaCount exactly 0 (maintenance stop) or 1 (single writer), with Recreate,
+process flock and bounded persistence unchanged. Fractions, strings, booleans,
+negative values and 2+ refuse; Helm decodes YAML/JSON integral 0.0/1.0 as the same
+numeric values as 0/1, so lexical float spelling is not distinguished. Actual
+Helm stopped/ready renders differ only in replicas and retain the same Platform
+source-rendered deployment projection. Owner preparation and independent
+coordinator checks approve this bounded source change only; 86 Helm renders and
+the existing outbox/delivery/receiver-port suite pass. No native stop, bootstrap,
+rollout, custody, public trust, resource or protected-effect approval follows.
+The proposed immutable image remains source revision
+`84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0`, ordinary CI402 verified digest
+`sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f`.
+This chart-only successor requires its own exact source/chart pin and reviewed
+runtime scope; it can explicitly select the unchanged verified 84 image without
+rebuilding it or claiming that its revision label names the new chart commit.
+These chart/test/workplan-only paths do not trigger the existing image workflow.
+Frozen rollout R1, Platform P2 and the package extraction2b/current05a remain
+unchanged. T06 stays `progress`; actual stopped-fence verification and all native
+admission gates remain open under the same existing task.

View file

@ -0,0 +1,40 @@
{
"schema": "access-engine.stopped-chart-source-proposal.v2",
"proposal_id": "FLEX24-STOPPED-CHART-20261016-R1",
"revision": "R2",
"base_revision": "84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0",
"prior_manifest_sha256": "sha256:53dfbfa1b3d4fd36f0fbe975083a46793b19d8b45d134cbe7fa3ebcc923daceb",
"isolated_source": "/tmp/access-stopped-chart-r2-asip4vy_",
"changed_paths": [
"charts/flex-auth/templates/deployment.yaml",
"charts/access-engine/templates/deployment.yaml",
"tests/test_outbox_replicas_chart.py",
"workplans/FLEX-WP-0024-decision-envelope-authenticity.md"
],
"preimages_sha256": {
"charts/flex-auth/templates/deployment.yaml": "sha256:b347614283e65d9e8006a93dfbdfec15360632f6c318761e245213460e158666",
"charts/access-engine/templates/deployment.yaml": "sha256:155310e75e3883fc25d9c9dcf79650a8216cf6fd09902421b856cdfeab5e4cc3",
"tests/test_outbox_replicas_chart.py": null,
"workplans/FLEX-WP-0024-decision-envelope-authenticity.md": "sha256:def6f73745d02ea88aa334db3239d2ef22984ebefa8853a5573496a08ed1592c"
},
"postimages_sha256": {
"charts/flex-auth/templates/deployment.yaml": "sha256:26e29618b9110bdd9d8aa73aae432a95c757a4f46e567ce4fe429c60bd519026",
"charts/access-engine/templates/deployment.yaml": "sha256:04f1fa3c0378f1f28b11438549e0542a85c0604fb19d7ea814eeddf8d99de29b",
"tests/test_outbox_replicas_chart.py": "sha256:dde2b054fa0355cf88034c3482aae45b4116d8ebdb9b0b9b28075960ebc244f0",
"workplans/FLEX-WP-0024-decision-envelope-authenticity.md": "sha256:3a3fc05528eb869204811d90d12ef5a3ac3b6d537b5b7a8cccbc15b6483a002f"
},
"files": {
"README.md": "sha256:4be2961c2a3ee90895c4f7fc816d78991cabb1f6030cb342dbae11e93c461aca",
"implementation-r2.patch": "sha256:14f1a8de20105760340d212b579d669a0e770d20accc433876f024601f206be1",
"postimages/charts/access-engine/templates/deployment.yaml": "sha256:04f1fa3c0378f1f28b11438549e0542a85c0604fb19d7ea814eeddf8d99de29b",
"postimages/charts/flex-auth/templates/deployment.yaml": "sha256:26e29618b9110bdd9d8aa73aae432a95c757a4f46e567ce4fe429c60bd519026",
"postimages/tests/test_outbox_replicas_chart.py": "sha256:dde2b054fa0355cf88034c3482aae45b4116d8ebdb9b0b9b28075960ebc244f0",
"postimages/workplans/FLEX-WP-0024-decision-envelope-authenticity.md": "sha256:3a3fc05528eb869204811d90d12ef5a3ac3b6d537b5b7a8cccbc15b6483a002f",
"r1-to-r2.patch": "sha256:28b22ff1480c9060c4758a29231803b32bf7cff2489fb644b9c2fd0f76c0edd2"
},
"source_checks": "86 Helm renders and existing outbox/delivery/receiver-port suite passed R1; R2 executable bytes unchanged; root independently reconstructed and passed",
"task_status_changed": false,
"canonical_owner_files_changed": false,
"native_admitted": false,
"execution_authorized": false
}

View file

@ -0,0 +1,207 @@
{{- if .Values.auditDelivery.enabled }}
{{- if ne (first .Values.args) "serve" }}
{{- fail "auditDelivery currently supports serve decision pins only" }}
{{- end }}
{{- if not .Values.outbox.enabled }}
{{- fail "auditDelivery requires enabled durable outbox" }}
{{- end }}
{{- if not (regexMatch "^https?://[^/?#@]+/v1/events$" .Values.auditDelivery.url) }}
{{- fail "auditDelivery.url must be an explicit http(s) /v1/events endpoint" }}
{{- end }}
{{- if or (not (hasPrefix "/" .Values.auditDelivery.mountPath)) (contains ".." .Values.auditDelivery.mountPath) }}
{{- fail "auditDelivery.mountPath must be an absolute normalized directory" }}
{{- end }}
{{- end }}
{{- if .Values.envelopeSigning.enabled }}
{{- if ne (first .Values.args) "serve" }}
{{- fail "envelopeSigning currently supports serve decision pins only" }}
{{- end }}
{{- if or (not (hasPrefix "/" .Values.envelopeSigning.mountPath)) (contains ".." .Values.envelopeSigning.mountPath) }}
{{- fail "envelopeSigning.mountPath must be an absolute normalized directory" }}
{{- end }}
{{- end }}
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: {{ .Release.Namespace }}
name: {{ include "access-engine.name" . }}
labels:
{{- include "access-engine.labels" . | nindent 4 }}
spec:
{{- if .Values.outbox.enabled }}
{{- $replicas := .Values.replicaCount }}
{{- $numericKind := has (kindOf $replicas) (list "int" "int64" "int32" "uint" "uint64" "float64" "float32") }}
{{- if or (not $numericKind) (not (or (eq (toString $replicas) "0") (eq (toString $replicas) "1"))) }}
{{- fail "outbox requires numeric integer replicaCount 0 (stopped) or 1 (single writer)" }}
{{- end }}
strategy:
type: Recreate
{{- end }}
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "access-engine.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "access-engine.labels" . | nindent 8 }}
spec:
automountServiceAccountToken: false
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
serviceAccountName: {{ include "access-engine.name" . }}
{{- end }}
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: access-engine
image: {{ include "access-engine.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
{{- toYaml .Values.args | nindent 12 }}
{{- if .Values.envelopeSigning.enabled }}
- --envelope-signing-key-file
- {{ printf "%s/seed" .Values.envelopeSigning.mountPath | quote }}
- --envelope-signing-kid
- {{ required "envelopeSigning.kid must be independently admitted" .Values.envelopeSigning.kid | quote }}
{{- end }}
{{- if .Values.outbox.enabled }}
{{- $limit := .Values.outbox.maxBytes }}
{{- $numericKind := has (kindOf $limit) (list "int" "int64" "int32" "uint" "uint64" "float64" "float32") }}
{{- $asFloat := float64 $limit }}
{{- $validNumber := and $numericKind (eq $asFloat (floor $asFloat)) }}
{{- $validString := and (kindIs "string" $limit) (regexMatch "^[1-9][0-9]*$" (toString $limit)) }}
{{- if or (not (or $validNumber $validString)) (lt $asFloat 1.0) (gt $asFloat 67108864.0) }}
{{- fail "outbox.maxBytes must be an exact integer between1 and67108864" }}
{{- end }}
- --outbox-max-bytes
- {{ printf "%.0f" $asFloat | quote }}
- --outbox-dir
- {{ .Values.outbox.mountPath | quote }}
- --emission-source
- {{ required "outbox.source is required when outbox.enabled=true" .Values.outbox.source | quote }}
{{- end }}
{{- if .Values.auditDelivery.enabled }}
- --audit-url
- {{ .Values.auditDelivery.url | quote }}
- --audit-token-file
- {{ printf "%s/token" .Values.auditDelivery.mountPath | quote }}
{{- end }}
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
{{- $binding := "" }}
{{- if not (and .Values.readback .Values.readback.enabled) }}
{{- $binding = required "callerAuth.binding is required when callerAuth.mode is warn or enforce" .Values.callerAuth.binding }}
{{- end }}
- --caller-audience
- {{ required "callerAuth.audience is required" .Values.callerAuth.audience | quote }}
- --caller-reviewer-token-file
- {{ printf "%s/token" .Values.callerAuth.reviewer.mountPath | quote }}
- --caller-ca-file
- {{ printf "%s/ca.crt" .Values.callerAuth.reviewer.mountPath | quote }}
- --caller-auth-mode
- {{ include "access-engine.callerAuth.mode" . | quote }}
- --caller-kubernetes-url
- {{ required "callerAuth.kubernetesURL is required when callerAuth.mode is warn or enforce" .Values.callerAuth.kubernetesURL | quote }}
{{- if and .Values.readback .Values.readback.enabled }}
{{- /* serve-readback admits one readback caller and no /v1/check binding (FLEX-DEC-2026-019). */}}
- --readback-caller
- {{ required "readback.caller is required when readback.enabled=true" .Values.readback.caller | quote }}
{{- else }}
- --caller-binding
- {{ $binding | quote }}
{{- range .Values.callerAuth.extraBindings }}
- --caller-binding
- {{ . | quote }}
{{- end }}
{{- end }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
livenessProbe:
httpGet:
path: {{ .Values.health.path }}
port: http
periodSeconds: 20
readinessProbe:
httpGet:
path: {{ .Values.health.path }}
port: http
periodSeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
{{- if or .Values.envelopeSigning.enabled .Values.outbox.enabled .Values.auditDelivery.enabled (eq (include "access-engine.callerAuth.enabled" .) "true") }}
volumeMounts:
{{- if .Values.envelopeSigning.enabled }}
- mountPath: {{ .Values.envelopeSigning.mountPath | quote }}
name: access-engine-envelope-signing
readOnly: true
{{- end }}
{{- if .Values.auditDelivery.enabled }}
- mountPath: {{ .Values.auditDelivery.mountPath | quote }}
name: access-engine-audit
readOnly: true
{{- end }}
{{- if .Values.outbox.enabled }}
- mountPath: {{ .Values.outbox.mountPath | quote }}
name: access-engine-outbox
{{- end }}
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
- mountPath: {{ .Values.callerAuth.reviewer.mountPath }}
name: access-engine-reviewer
readOnly: true
{{- end }}
{{- end }}
{{- if or .Values.envelopeSigning.enabled .Values.outbox.enabled .Values.auditDelivery.enabled (eq (include "access-engine.callerAuth.enabled" .) "true") }}
volumes:
{{- if .Values.envelopeSigning.enabled }}
- name: access-engine-envelope-signing
secret:
secretName: {{ required "envelopeSigning.secretName must name an admitted existing Secret" .Values.envelopeSigning.secretName | quote }}
defaultMode: 0440
items:
- key: {{ required "envelopeSigning.secretKey is required" .Values.envelopeSigning.secretKey | quote }}
path: seed
{{- end }}
{{- if .Values.auditDelivery.enabled }}
- name: access-engine-audit
secret:
secretName: {{ required "auditDelivery.secretName must name an admitted existing Secret" .Values.auditDelivery.secretName | quote }}
defaultMode: 0440
items:
- key: {{ required "auditDelivery.secretKey is required" .Values.auditDelivery.secretKey | quote }}
path: token
{{- end }}
{{- if .Values.outbox.enabled }}
- name: access-engine-outbox
persistentVolumeClaim:
claimName: {{ default (printf "%s-outbox" (include "access-engine.name" .)) .Values.outbox.existingClaim | quote }}
{{- end }}
{{- if eq (include "access-engine.callerAuth.enabled" .) "true" }}
- name: access-engine-reviewer
projected:
defaultMode: 0440
sources:
- serviceAccountToken:
{{- if .Values.callerAuth.reviewer.audience }}
audience: {{ .Values.callerAuth.reviewer.audience | quote }}
{{- end }}
expirationSeconds: {{ .Values.callerAuth.reviewer.expirationSeconds }}
path: token
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt
{{- end }}
{{- end }}

View file

@ -0,0 +1,202 @@
{{- if .Values.auditDelivery.enabled }}
{{- if ne (first .Values.args) "serve" }}
{{- fail "auditDelivery currently supports serve decision pins only" }}
{{- end }}
{{- if not .Values.outbox.enabled }}
{{- fail "auditDelivery requires enabled durable outbox" }}
{{- end }}
{{- if not (regexMatch "^https?://[^/?#@]+/v1/events$" .Values.auditDelivery.url) }}
{{- fail "auditDelivery.url must be an explicit http(s) /v1/events endpoint" }}
{{- end }}
{{- if or (not (hasPrefix "/" .Values.auditDelivery.mountPath)) (contains ".." .Values.auditDelivery.mountPath) }}
{{- fail "auditDelivery.mountPath must be an absolute normalized directory" }}
{{- end }}
{{- end }}
{{- if .Values.envelopeSigning.enabled }}
{{- if ne (first .Values.args) "serve" }}
{{- fail "envelopeSigning currently supports serve decision pins only" }}
{{- end }}
{{- if or (not (hasPrefix "/" .Values.envelopeSigning.mountPath)) (contains ".." .Values.envelopeSigning.mountPath) }}
{{- fail "envelopeSigning.mountPath must be an absolute normalized directory" }}
{{- end }}
{{- end }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "flex-auth.name" . }}
labels:
{{- include "flex-auth.labels" . | nindent 4 }}
spec:
{{- if .Values.outbox.enabled }}
{{- $replicas := .Values.replicaCount }}
{{- $numericKind := has (kindOf $replicas) (list "int" "int64" "int32" "uint" "uint64" "float64" "float32") }}
{{- if or (not $numericKind) (not (or (eq (toString $replicas) "0") (eq (toString $replicas) "1"))) }}
{{- fail "outbox requires numeric integer replicaCount 0 (stopped) or 1 (single writer)" }}
{{- end }}
strategy:
type: Recreate
{{- end }}
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "flex-auth.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "flex-auth.labels" . | nindent 8 }}
spec:
automountServiceAccountToken: false
{{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }}
serviceAccountName: {{ include "flex-auth.name" . }}
{{- end }}
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
seccompProfile:
type: RuntimeDefault
containers:
- name: flex-auth
image: {{ include "flex-auth.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
{{- toYaml .Values.args | nindent 12 }}
{{- if .Values.envelopeSigning.enabled }}
- --envelope-signing-key-file
- {{ printf "%s/seed" .Values.envelopeSigning.mountPath | quote }}
- --envelope-signing-kid
- {{ required "envelopeSigning.kid must be independently admitted" .Values.envelopeSigning.kid | quote }}
{{- end }}
{{- if .Values.outbox.enabled }}
{{- $limit := .Values.outbox.maxBytes }}
{{- $numericKind := has (kindOf $limit) (list "int" "int64" "int32" "uint" "uint64" "float64" "float32") }}
{{- $asFloat := float64 $limit }}
{{- $validNumber := and $numericKind (eq $asFloat (floor $asFloat)) }}
{{- $validString := and (kindIs "string" $limit) (regexMatch "^[1-9][0-9]*$" (toString $limit)) }}
{{- if or (not (or $validNumber $validString)) (lt $asFloat 1.0) (gt $asFloat 67108864.0) }}
{{- fail "outbox.maxBytes must be an exact integer between1 and67108864" }}
{{- end }}
- --outbox-max-bytes
- {{ printf "%.0f" $asFloat | quote }}
- --outbox-dir
- {{ .Values.outbox.mountPath | quote }}
- --emission-source
- {{ required "outbox.source is required when outbox.enabled=true" .Values.outbox.source | quote }}
{{- end }}
{{- if .Values.auditDelivery.enabled }}
- --audit-url
- {{ .Values.auditDelivery.url | quote }}
- --audit-token-file
- {{ printf "%s/token" .Values.auditDelivery.mountPath | quote }}
{{- end }}
{{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }}
{{- $binding := "" }}
{{- if not (and .Values.readback .Values.readback.enabled) }}
{{- $binding = required "callerAuth.binding is required when callerAuth.mode is warn or enforce" .Values.callerAuth.binding }}
{{- end }}
- --caller-audience
- {{ required "callerAuth.audience is required" .Values.callerAuth.audience | quote }}
- --caller-auth-mode
- {{ include "flex-auth.callerAuth.mode" . | quote }}
- --caller-kubernetes-url
- {{ required "callerAuth.kubernetesURL is required when callerAuth.mode is warn or enforce" .Values.callerAuth.kubernetesURL | quote }}
{{- if and .Values.readback .Values.readback.enabled }}
{{- /* serve-readback admits one readback caller and no /v1/check binding (FLEX-DEC-2026-019). */}}
- --readback-caller
- {{ required "readback.caller is required when readback.enabled=true" .Values.readback.caller | quote }}
{{- else }}
- --caller-binding
- {{ $binding | quote }}
{{- range .Values.callerAuth.extraBindings }}
- --caller-binding
- {{ . | quote }}
{{- end }}
{{- end }}
{{- end }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
livenessProbe:
httpGet:
path: {{ .Values.health.path }}
port: http
periodSeconds: 20
readinessProbe:
httpGet:
path: {{ .Values.health.path }}
port: http
periodSeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
{{- if or .Values.envelopeSigning.enabled .Values.outbox.enabled .Values.auditDelivery.enabled (eq (include "flex-auth.callerAuth.enabled" .) "true") }}
volumeMounts:
{{- if .Values.envelopeSigning.enabled }}
- mountPath: {{ .Values.envelopeSigning.mountPath | quote }}
name: flex-auth-envelope-signing
readOnly: true
{{- end }}
{{- if .Values.auditDelivery.enabled }}
- mountPath: {{ .Values.auditDelivery.mountPath | quote }}
name: flex-auth-audit
readOnly: true
{{- end }}
{{- if .Values.outbox.enabled }}
- mountPath: {{ .Values.outbox.mountPath | quote }}
name: flex-auth-outbox
{{- end }}
{{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }}
- mountPath: {{ .Values.callerAuth.reviewer.mountPath }}
name: flex-auth-reviewer
readOnly: true
{{- end }}
{{- end }}
{{- if or .Values.envelopeSigning.enabled .Values.outbox.enabled .Values.auditDelivery.enabled (eq (include "flex-auth.callerAuth.enabled" .) "true") }}
volumes:
{{- if .Values.envelopeSigning.enabled }}
- name: flex-auth-envelope-signing
secret:
secretName: {{ required "envelopeSigning.secretName must name an admitted existing Secret" .Values.envelopeSigning.secretName | quote }}
defaultMode: 0440
items:
- key: {{ required "envelopeSigning.secretKey is required" .Values.envelopeSigning.secretKey | quote }}
path: seed
{{- end }}
{{- if .Values.auditDelivery.enabled }}
- name: flex-auth-audit
secret:
secretName: {{ required "auditDelivery.secretName must name an admitted existing Secret" .Values.auditDelivery.secretName | quote }}
defaultMode: 0440
items:
- key: {{ required "auditDelivery.secretKey is required" .Values.auditDelivery.secretKey | quote }}
path: token
{{- end }}
{{- if .Values.outbox.enabled }}
- name: flex-auth-outbox
persistentVolumeClaim:
claimName: {{ default (printf "%s-outbox" (include "flex-auth.name" .)) .Values.outbox.existingClaim | quote }}
{{- end }}
{{- if eq (include "flex-auth.callerAuth.enabled" .) "true" }}
- name: flex-auth-reviewer
projected:
defaultMode: 0440
sources:
- serviceAccountToken:
{{- if .Values.callerAuth.reviewer.audience }}
audience: {{ .Values.callerAuth.reviewer.audience | quote }}
{{- end }}
expirationSeconds: {{ .Values.callerAuth.reviewer.expirationSeconds }}
path: token
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt
{{- end }}
{{- end }}

View file

@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Actual Helm stop/start renders and unsafe replica value rejection; offline only."""
import copy
import json
from pathlib import Path
import subprocess
import tempfile
import yaml
ROOT = Path(__file__).resolve().parents[1]
def render(chart, value, encoding="yaml", success=True):
with tempfile.TemporaryDirectory() as directory:
command = ["helm", "template", "flex-auth-secrets-engine", str(ROOT / "charts" / chart),
"--namespace", "flex-auth", "-f", str(ROOT / "values/secrets-engine.yaml"),
"--set", "outbox.enabled=true", "--set", "outbox.source=flex-auth.secrets-engine",
"--set", "envelopeSigning.enabled=true", "--set", "envelopeSigning.kid=test-kid",
"--set", "envelopeSigning.secretName=proposed-seed", "--set", "envelopeSigning.secretKey=seed"]
if encoding == "yaml":
overlay = Path(directory) / "values.yaml"
overlay.write_text(yaml.safe_dump({"replicaCount": value}))
command += ["-f", str(overlay)]
elif encoding == "json":
command += ["--set-json", "replicaCount=" + json.dumps(value)]
elif encoding == "string":
command += ["--set-string", "replicaCount=" + str(value)]
else:
command += ["--set", "replicaCount=" + str(value)]
result = subprocess.run(command, capture_output=True, text=True)
if not success:
assert result.returncode != 0, (chart, encoding, value)
assert "replicaCount" in result.stderr, result.stderr
return
assert result.returncode == 0, result.stderr
documents = [d for d in yaml.safe_load_all(result.stdout) if d]
deployment = next(d for d in documents if d["kind"] == "Deployment")
assert deployment["spec"]["strategy"] == {"type": "Recreate"}
assert type(deployment["spec"]["replicas"]) is int
assert deployment["spec"]["replicas"] == value
return documents
count = 0
for chart in ("flex-auth", "access-engine"):
for encoding in ("yaml", "json", "cli"):
stopped = render(chart, 0, encoding)
ready = render(chart, 1, encoding)
normalized = copy.deepcopy(ready)
next(d for d in normalized if d["kind"] == "Deployment")["spec"]["replicas"] = 0
assert stopped == normalized, "stop must change replicas only, including identical pod projection/PVC"
count += 2
for encoding in ("yaml", "json"):
for value in (-1, 2, 1.5, 0.5, True, False, None, "0", "1", [], {}):
render(chart, value, encoding, success=False)
count += 1
# Helm loses lexical 0 vs0.0 and1 vs1.0 before template evaluation.
for value in (0.0, 1.0):
render(chart, value, encoding)
count += 1
for value in ("-1", "2", "0.5", "1.5", "true", "false", "null", "0.0", "1.0"):
render(chart, value, "cli", success=False)
count += 1
for value in ("0", "1"):
render(chart, value, "string", success=False)
count += 1
print(f"stopped/single-writer chart checks passed: {count} actual Helm renders")

View file

@ -0,0 +1,327 @@
---
id: FLEX-WP-0024
type: workplan
title: "Sign the decision envelope: the response channel is unauthenticated"
domain: infotech
repo: flex-auth
status: active
owner: claude
topic_slug: netkingdom
planning_priority: P1
planning_order: 240
depends_on_workplans:
- FLEX-WP-0021
related_workplans:
- FLEX-WP-0019
- FLEX-WP-0023
created: "2026-09-06"
updated: "2026-10-16"
state_hub_workstream_id: "90577acd-6910-548d-a13e-1dbfdfb8ed27"
---
# FLEX-WP-0024 — Sign the decision envelope: the response channel is unauthenticated
Opened by `FLEX-DEC-2026-010`, from a question `secrets-engine` asked rather
than answering locally: **how is the response channel authenticated?** They
declined to choose a transport control for flex-auth's service on the grounds
that it is not a consumer's call. Correct, and it is why this is a flex-auth
workplan and not a mitigation in their repo.
The stance today: pins serve plain HTTP, `flex-auth.decision-record.v1` carries
no signature, and a responder that knows the package id and version — both
published — can return a well-formed `effect: allow` that passes every check a
consumer performs.
## The two things not to get backwards
**The digests do not authenticate the responder and cannot.** Every input to
them is either sent by the caller or published, so a forger reproduces
`request_digest`, `policy_package_digest`, and `registry_snapshot_digest`
exactly. Publishing more digests makes a forged envelope look *more*
authenticated, not less.
**Fail-closed does not cover this.** It protects against a PDP that is absent,
not against one that lies. An unreachable PDP denies; a lying PDP allows.
## 1. Publish the stance and stop publishing bare Service names
```task
id: FLEX-WP-0024-T01
status: done
priority: high
state_hub_task_id: "b8fad80d-5c44-5e26-a632-5096e0c0f3c5"
```
Owner: `flex-auth`.
**Done 2026-09-06.** `docs/decision-record-contract.md` states that digest
recomputation is not responder verification, because the contract is the only
document a consumer is guaranteed to read.
`docs/operator-caller-access-path.md` carries the reproduced DNS probe and the
channel analysis. The trailing-dot FQDN replaced the bare Service name in
`examples/secrets-engine/README.md`, `SCOPE.md`, and the `FLEX-WP-0021-T05`
handover note — a bare `*.svc.cluster.local` name resolves through
`search ad.binect.de` to one unrelated public host, and a name for a service
that does not exist resolves to the same address.
## 2. Choose the signature shape and key custody
```task
id: FLEX-WP-0024-T02
status: done
priority: high
state_hub_task_id: "5482f3cd-36cb-55e0-8c52-0ca2340ed4d7"
```
Owner: `flex-auth`; key custody is **not** ours to invent.
- Detached signature over the canonical envelope, preferred over transport-only
mTLS for two reasons: it survives the transport, so a decision audited later
is still attributable, where mTLS authenticates a connection that no longer
exists; and it composes with `FLEX-DEC-2026-009`, since a record that names
its authenticated caller *and* carries flex-auth's signature is an audit
artifact where either alone is a claim.
- Decide the canonical form to sign. The existing canonical-JSON rules from
`docs/canonical-request-digest.md` are the obvious basis, but the signed
material is the **envelope**, not the request, and it must exclude the
signature field itself. Write it down before implementing: an implicit
canonicalisation rule is what `FLEX-DEC-2026-007` had to correct.
- **Do not mint a key in this repo.** Route custody via
`warden route find "decision envelope signing key"` and OpenBao, per the
estate credential-routing rule. flex-auth decides the shape; it does not own
key material.
- State key rotation and how a consumer discovers the current public key, since
a consumer that cannot rotate trust has a pinned key that will one day be
wrong.
Gate: the canonical form, the algorithm, and the custody path are written down
and reviewed before any signing code exists.
**Done 2026-09-14.** Written in `docs/decision-envelope-signature.md`: detached
Ed25519 over `json.Marshal` of the envelope with `signature` omitted; unsigned
is `{mode: unsigned}`; production key lives in OpenBao, not this repo
(proposed catalog id `flex-auth-decision-envelope-signing-key`). Test-only seed
is documented and is not the custody path.
## 3. Implement signing and verification
```task
id: FLEX-WP-0024-T03
status: done
priority: high
state_hub_task_id: "041612ea-1be4-5997-8c32-49f8bcc50855"
```
Owner: `flex-auth`.
- Add the signature to `schemas/decision_envelope.schema.json` as additive, and
state in the contract that `request_digest` is unaffected so no consumer
re-pins — the same note that was needed for v2 and for
`approval_binding_digest`.
- Emit unsigned when no key is configured, and say so in the envelope rather
than omitting the field silently. Absence stated, not implied — the
`pdp_digest` precedent.
- Ship a verification example and a fixture pair in
`examples/secrets-engine/replay/`: a genuine signed envelope and one whose
payload was altered after signing, so a consumer's verifier is tested against
a real failure rather than only a real success. **A verifier that has never
seen an invalid signature is untested**, which is the lesson from every
fixture suite in `FLEX-DEC-2026-008`.
Gate: an altered envelope fails verification, an unaltered one passes, and a
consumer can perform both from the fixtures alone.
**Done 2026-09-14.** Additive `signature` on the schema and envelope. Engine
emits `{mode: unsigned}` when no key is configured. Fixture pair:
`examples/secrets-engine/replay/decision_rotate_signed.json` verifies,
`decision_rotate_signed_tampered.json` does not, same `request_digest`. Public
key in `keys.json`. Tests in `internal/sign`.
## 4. Report the gap to gate-house
```task
id: FLEX-WP-0024-T04
status: done
priority: medium
state_hub_task_id: "82d6b75e-e2c6-5e3d-a897-fbdfb7c9094e"
```
Owner: `flex-auth`.
§6.4 obligates the PEP to act on a decision and §9.3 splits degraded mode by
owner, but **nothing in the standard requires a PDP's response to be
authenticable**. So a fully conformant PEP can trust a forged allow while
meeting every published obligation. That is a gap in the standard, not only in
this implementation, and it belongs in the outstanding v0.8 assent round
alongside the `FLEX-DEC-2026-009` finding.
## T04 closed — 2026-09-07
**Done.** Reported as `FLEX-DEC-2026-011` F1, ranked blocking for
calling the §6.4 PEP obligations complete and explicitly not blocking anyone's
adoption of v0.8 — the condition already exists under v0.7 and is not created by
this version. §16 already carries the same observation one layer up, about the
standard's own publication path having no digest or freeze discipline; F1 is
that observation applied to the artifact the standard regulates.
## 5. Wire owner signing into the decision pin and retained deployment charts
```task
id: FLEX-WP-0024-T05
status: done
priority: high
state_hub_task_id: "cfe4d275-9f02-5090-8391-06eea59d45c5"
```
2026-10-16 source follow-through: NetKingdom target Secrets Engine R2 owner review
found that the completed T03 signature package was used only by fixtures. Add
explicit mounted seed/kid configuration for `serve`, sign the final envelope before
recording, and expose inert existing-Secret mounts in both retained/new charts.
The API/sign verifier sources remain byte-identical. Local source validation and
review are in the Custodian `docs/changes/netkingdom-target-2026-10-16/access-verifier-review/`.
This is source preparation only, not signer admission or deployed acceptance.
## 6. Admit custody, consumer trust, exact packages and native signed decisions
```task
id: FLEX-WP-0024-T06
status: progress
priority: high
state_hub_task_id: "7f785b52-bf62-5884-8d34-670d70bc9023"
```
Wait on Platform/OpenBao key custody, independent public-key and issuer/policy-pin
admission, exact image and existing-Secret values rollout, owner-bound caller/tunnel,
and Secrets Engine verifier installation. Follow `docs/decision-envelope-signature.md`
for overlap/removal semantics. Retain signed producer record/consumer acceptance,
forged/altered/unsigned/wrong-pin/expired denial and rotation-removal receipts;
then update actual deployed status. No signer or runtime trust is admitted by the
source review. This task owns the producer-side native gate; SECRETS-WP-0014 owns
consumer admission/enforcement. The old fixture completion never established this
gate, so the workplan is reopened rather than claiming it already passed.
2026-10-16 owner native preparation: T06 is `progress` because Access still owns
signer workload declaration, artifact/provenance and source/provider-join preparation;
no unqualified cross-owner wait is claimed. Read-only actual release metadata and
bounded proposal are in the Custodian
`docs/changes/netkingdom-target-2026-10-16/access-native-preparation/`.
The real Platform custody route `flex-auth-decision-envelope-signing-key` exists but
is draft/nonresolvable; no production kid/public fingerprint, Secret projection or
custody receipt is admitted. Coordinate that named commitment through CUST-WP-0080
instead of inventing a Platform task/approval. SECRETS-WP-0014-T03 owns installed
consumer native verification and must not form a circular blocking dependency.
The first proposed native trial only signs the unchanged lifecycle package on the
existing retained release; `execute-ccr` remains denied. Current service-pilot
`runtime_admission := false` remains mandatory: the source ServicePilotInputs is
not yet wired into HTTP, and public service_actor_binding never authenticates the
caller. Later activation needs a complete reviewed provider join, exact policy and
operation admission, artifact digests, owner execution/rollback proposal and native
receipts. Preparation/local synthetic success grants none of those permissions.
2026-10-16 source-only native preparation adds the existing Secrets PDP ServiceAccount
to `tenancy.yaml` with evidence in `docs/native-signer-workload.json`, makes the
retained `flex-auth` audience explicit, pins CI archive fetch to the full source
revision, and adds offline native signer receipt verification using the existing
Secrets verifier. No key, trust, image publication or live activation is admitted.
The managed workload's missing rapp declaration remains explicit migration debt;
this source binding does not fabricate a package declaration or raise its grade.
2026-10-16 separate B1 source follow-through wires ServicePilotInputs through actual
HTTP/TokenReview into a signed-DENY-only engine entry. Generic `execute-ccr` calls
refuse across every policy, including the old separately named positive fixture;
that proof stays pinned to ec21518e. Original context/submitted digest stay unchanged.
Actual disposable serve/TLS TokenReview/HTTP signed-DENY and bypass/refusal tests are
source evidence only. B2 depends on independently reviewed Approval fact contract
and canonical vectors; B3 additionally needs exact source/normalization numeric
representation and full trusted provider joins. No current policy false gate,
production trust, native deployment or protected effect is enabled by B1.
2026-10-16 B2/B3 source successor preparation (separate from frozen B1): the
isolated v2 source path now joins the fixed authenticated Approval reader,
unchanged Approval-owner canonical/fact helper, original/fresh correspondence,
raw numeric request/registry source retention, same policy evaluator and signed
source provenance. Disposable actual HTTP and production serve subprocess prove
synthetic signed ALLOW and fail-closed negative joins with exact response/record
parity. See `docs/trusted-service-task-source.md`. T06 remains `progress`: this
source proposal requires independent cross-owner review and full consumer/Platform
successor integration, then separately admitted runtime custody, source trust,
image provenance and rollout. No source-only proof establishes those runtime gates.
2026-10-16 B2/B3 R2 temporal correction (R1 retained): whole-stack review exposed
that R1's 15s ALLOW and default Approval freshness30 cannot fit the declared
minimum backendTTL30 plus existing cleanupreserve5. The v2 successor now derives
exact ALLOW expiry from the immutable operationdeadline, requiring all original/
fresh approval/freshness/caller bounds, a conservative source maximum120 and
remaining declaredTTL+5 after source I/O/evaluation. No consumer/native deadline
or cleanup guard is relaxed. Max structural budgets that cannot fit refuse.
The fixture declares backendTTL30/deadline60 and explicit source freshness120;
owner runtime source freshness configuration and full native integration remain
separate reviewed gates. See docs/trusted-service-task-source.md.
2026-10-16 B2/B3 R3 follows actual retained-registry integration failure: subject
enrichment changed evaluated approval-free digest from the original proposal.
The existing signed claim-free DENY now explicitly carries v2 approvalfree digest
(equal evaluated requestdigest), enabling producer-owned preapproval facts under
the already published enrichment contract. The final original proposal adds only
the issued claim; registry drift/refreshed facts cannot silently rebind it.
Retained-registry HTTP and production serve proofs and negative joins pass.
R3 preserves precise notbefore pending separate Approval-owner timestamp fidelity
repair; no rounding waiver, copied registry algorithm or empty-registry workaround.
2026-10-16 native admission prerequisite: fresh metadata confirms no namespace
PVC exists, and current outbox had neither OS writer exclusion nor a byte/startup
bound. Prepared existing-outbox hardening on exact canonical R3 base2b064ee:
nonblocking Linux flock held through Close/process lifetime before recovery,
64MiB source journal ceiling,2MiB record/atomic-batch bounds, streaming startup,
4KiB cursor, all-effect commit refusal and fenced unconfirmed rollback. Actual
process contention/crash release, capacity/prefix/restart and ENOSPC/fsync failure
proofs are part of the owner package. No new service, live mount, deletion or
custody grant; exact published successor, new PVC/PV retention/managed-package
owner disposition and independent consumer admission remain prerequisites.
2026-10-16 owner package extraction record: canonical local package
`rapp-flex-auth-secrets-engine` now exists at commit
`d52f811ed5ec0be27c9c367e71d476125b3228be`, tree
`d4fbad71bdc8ef870603526ccd1f53195d0322af`; authoritative declaration
`declarations/rapp.yaml` SHA256 `0b722431bc5162dd86f72b15f12424e149aca8f2a8790adf2c57e8511ea73b4c`.
Repo Manager exact local resolution under `/home/worsch` returns
`rapp-flex-auth-secrets-engine` / workload `flex-auth-secrets-engine` /
deployable `flex-auth-secrets-engine`, ownership `access-engine`, readiness
`draft`. The retained lifecycle PDP is the only rollback boundary claimed;
tenant/user/canary and retired `rapp-secrets-engine` remain excluded. Normative
focused family validation passes; existing unrelated global rapp-forgejo error
and reef projection warning remain visible. Package extraction keeps source
`2b064eee510640224274ccdc7cefceb1ae13162e` and current image
`sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd`
unchanged. Exact local source resolution is no central/private read-placement,
reef binding, custody, native rollout or consumer trust admission. Future signer
image/config adoption remains separate. T06 remains `progress`; no second work
item duplicates this bounded dependency.
2026-10-16 bounded stopped-chart source preparation (Custodian
`access-native-admission/stopped-chart-r2`): both outbox charts admit numeric
replicaCount exactly 0 (maintenance stop) or 1 (single writer), with Recreate,
process flock and bounded persistence unchanged. Fractions, strings, booleans,
negative values and 2+ refuse; Helm decodes YAML/JSON integral 0.0/1.0 as the same
numeric values as 0/1, so lexical float spelling is not distinguished. Actual
Helm stopped/ready renders differ only in replicas and retain the same Platform
source-rendered deployment projection. Owner preparation and independent
coordinator checks approve this bounded source change only; 86 Helm renders and
the existing outbox/delivery/receiver-port suite pass. No native stop, bootstrap,
rollout, custody, public trust, resource or protected-effect approval follows.
The proposed immutable image remains source revision
`84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0`, ordinary CI402 verified digest
`sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f`.
This chart-only successor requires its own exact source/chart pin and reviewed
runtime scope; it can explicitly select the unchanged verified 84 image without
rebuilding it or claiming that its revision label names the new chart commit.
These chart/test/workplan-only paths do not trigger the existing image workflow.
Frozen rollout R1, Platform P2 and the package extraction2b/current05a remain
unchanged. T06 stays `progress`; actual stopped-fence verification and all native
admission gates remain open under the same existing task.

View file

@ -0,0 +1,29 @@
--- a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
+++ b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
@@ -302,3 +302,26 @@
reef binding, custody, native rollout or consumer trust admission. Future signer
image/config adoption remains separate. T06 remains `progress`; no second work
item duplicates this bounded dependency.
+
+
+2026-10-16 bounded stopped-chart source preparation (Custodian
+`access-native-admission/stopped-chart-r2`): both outbox charts admit numeric
+replicaCount exactly 0 (maintenance stop) or 1 (single writer), with Recreate,
+process flock and bounded persistence unchanged. Fractions, strings, booleans,
+negative values and 2+ refuse; Helm decodes YAML/JSON integral 0.0/1.0 as the same
+numeric values as 0/1, so lexical float spelling is not distinguished. Actual
+Helm stopped/ready renders differ only in replicas and retain the same Platform
+source-rendered deployment projection. Owner preparation and independent
+coordinator checks approve this bounded source change only; 86 Helm renders and
+the existing outbox/delivery/receiver-port suite pass. No native stop, bootstrap,
+rollout, custody, public trust, resource or protected-effect approval follows.
+The proposed immutable image remains source revision
+`84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0`, ordinary CI402 verified digest
+`sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f`.
+This chart-only successor requires its own exact source/chart pin and reviewed
+runtime scope; it can explicitly select the unchanged verified 84 image without
+rebuilding it or claiming that its revision label names the new chart commit.
+These chart/test/workplan-only paths do not trigger the existing image workflow.
+Frozen rollout R1, Platform P2 and the package extraction2b/current05a remain
+unchanged. T06 stays `progress`; actual stopped-fence verification and all native
+admission gates remain open under the same existing task.

View file

@ -0,0 +1,67 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — access-engine
**Domain:** infotech
**Last synced:** 2026-10-16 14:30 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
### Sign the decision envelope: the response channel is unauthenticated
Progress: 5/6 done | workplan_id: `90577acd-6910-548d-a13e-1dbfdfb8ed27`
**Open tasks:**
- ► 6. Admit custody, consumer trust, exact packages and native signed decisions `7f785b52`
### The decision record has a declared emission guarantee and nothing that delivers it
Progress: 5/9 done | workplan_id: `84f5d9fe-b4c9-584a-b964-efe3e48af095`
**Open tasks:**
- ! 2. Register flex-auth as an audit-core sender `89661908`
*(wait: GH-WP-0004-T12 refused the load-bearing failure-path exception (GH-DEC-2026-024). AUDIT-IN-0006 sender rows and governed token delivery remain pending.)*
- ! 4. Close G2 `423b3090`
*(wait: G2 needs T08 native heartbeat/drain and silence-finding evidence plus T06 reconciliation; local tests cannot close the delivery gap.)*
- ! 6. Reconciliation, profile check, and storage `bf92a951`
*(wait: Receiver counts use accepted_at, while local counts use commit time; audit-core must supply compatible window semantics or durable acceptance timestamps before a valid comparator can be implemented.)*
- ! 8. Admit and exercise native heartbeat/drain delivery `b3b52b30`
*(wait: Needs admitted sender/token mounts, founder approval of exact image/PVC/network rollout, and audit-core observer evidence; no production delivery is enabled.)*
### Runtime rename: flex-auth to access-engine, run side by side, consumers switch one at a time
Progress: 0/8 done | workplan_id: `af0f8d77-af04-569b-8e79-6c47eb8f1087`
**Open tasks:**
- ! Decide the target names and supersede the retain table `4f9a796d`
- ! Make the PDP accept both names `487525e5`
- ! Rename the source `ff7fd369`
- ! Stand up the access-engine namespace next to flex-auth `13b93d7a`
- ! Consumers switch over, one at a time `f0470e5c`
- ► Inventory every consumer reference and open handoffs `8f597899`
- · Retire the flex-auth runtime `e46c9c8e`
- … and 1 more open tasks
### Repository identity migration from flex-auth to access-engine
Progress: 10/11 done | workplan_id: `99a661a8-b36c-5c1c-b78b-1e8930bcd0a9`
**Open tasks:**
- ! 11. Soak, hand off residuals, and clean up the old checkout `46d8318b`
*(wait: Cleanup approved conditionally (e071b34e): runs once the listed soak evidence, SBOM-IN-0001 receipt and residual handoffs exist.)*
### 2026-10-03 secret-read evaluator for ops-warden fetch checks
Progress: 1/2 done | workplan_id: `60941288-d1f8-58fe-ad38-d508b7d5eac2`
**Open tasks:**
- ! Deploy the pin (founder go required) `aa00b29f`
*(wait: Install accepted retroactively (877c268b); steps 6-7 follow WARDEN-WP-0039-T03 token receipts and fetch URL.)*
## Inbox Hygiene
**Stale unread:** 5 message(s) older than 3 day(s) — triage at session start.
**Missing thread_id:** 2 unread message(s) lack supersession chains.
---
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("infotech")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

View file

@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""Integrate the approved owner record; preserve the unrelated staged brief."""
import hashlib
import json
import subprocess
from pathlib import Path
HERE = Path(__file__).resolve().parent
REPO = Path('/home/worsch/access-engine')
WP = 'workplans/FLEX-WP-0024-decision-envelope-authenticity.md'
BRIEF = '.custodian-brief.md'
def git(*args):
return subprocess.check_output(['git', '-C', str(REPO), *args])
def sha(data):
return hashlib.sha256(data).hexdigest()
def save(name, data):
(HERE / name).write_text(json.dumps(data, indent=2) + '\n')
meta = json.loads((HERE / 'own-record-metadata.json').read_text())
assert sha((REPO / WP).read_bytes()) == meta['source_sha256'], 'Workplan preimage changed'
assert sha((HERE / 'own-record.patch').read_bytes()) == meta['patch_sha256']
assert git('diff', '--cached', '--name-only').decode().splitlines() == [BRIEF], 'Unexpected staged paths'
brief_index = git('ls-files', '--stage', '--', BRIEF).decode().strip()
brief_worktree = (REPO / BRIEF).read_bytes()
head_before = git('rev-parse', 'HEAD').decode().strip()
records_before = sha((REPO / 'WORK-RECORDS.md').read_bytes())
save('integration-preimage.json', {'head': head_before, 'workplan_sha256':meta['source_sha256'],
'brief_index':brief_index, 'brief_worktree_sha256':sha(brief_worktree),
'work_records_sha256':records_before, 'staged_paths':[BRIEF]})
(HERE / 'brief.preexisting.bytes').write_bytes(brief_worktree)
git('apply', '--check', str(HERE / 'own-record.patch'))
git('apply', str(HERE / 'own-record.patch'))
assert sha((REPO / WP).read_bytes()) == meta['proposed_result_sha256']
assert git('ls-files', '--stage', '--', BRIEF).decode().strip() == brief_index
assert (REPO / BRIEF).read_bytes() == brief_worktree
commit_output = git('commit', '--only', '-m', 'docs: record Access owner native execution plan review', '--', WP).decode()
owner_commit = git('rev-parse', 'HEAD').decode().strip()
assert git('diff-tree', '--no-commit-id', '--name-only', '-r', owner_commit).decode().splitlines() == [WP]
assert git('ls-files', '--stage', '--', BRIEF).decode().strip() == brief_index
assert (REPO / BRIEF).read_bytes() == brief_worktree
head_brief = git('ls-tree', 'HEAD', '--', BRIEF).decode().split()
git('update-index', '--cacheinfo', head_brief[0], head_brief[2], BRIEF)
assert git('diff', '--cached', '--name-only').decode().strip() == ''
try:
result = subprocess.run(['statehub', 'fix-consistency', '--repo', 'access-engine',
'--repo-path', str(REPO), '--no-writeback', '--json'],
capture_output=True, text=True)
(HERE / 'sync.stdout.json').write_text(result.stdout)
(HERE / 'sync.stderr.txt').write_text(result.stderr)
after_brief = (REPO / BRIEF).read_bytes()
after_index = git('ls-files', '--stage', '--', BRIEF).decode().strip()
# Restore only the captured unrelated brief; race-check both before writing.
assert (REPO / BRIEF).read_bytes() == after_brief
assert git('ls-files', '--stage', '--', BRIEF).decode().strip() == after_index
mode, blob, stage_path = brief_index.split(maxsplit=2)
assert stage_path == '0\t' + BRIEF
(REPO / BRIEF).write_bytes(brief_worktree)
git('update-index', '--cacheinfo', mode, blob, BRIEF)
assert (REPO / BRIEF).read_bytes() == brief_worktree
assert git('ls-files', '--stage', '--', BRIEF).decode().strip() == brief_index
save('integration-receipt.json', {'owner_commit':owner_commit,
'owner_commit_paths':[WP], 'sync_return_code':result.returncode,
'sync_stdout_sha256':sha(result.stdout.encode()),
'sync_stderr_sha256':sha(result.stderr.encode()),
'head_after_sync':git('rev-parse', 'HEAD').decode().strip(),
'upstream':git('rev-parse', '@{upstream}').decode().strip(),
'brief_index_exactly_restored':True, 'brief_worktree_exactly_restored':True,
'work_records_before_sha256':records_before,
'work_records_after_sha256':sha((REPO / 'WORK-RECORDS.md').read_bytes()),
'workplan_after_sha256':sha((REPO / WP).read_bytes()),
'status':git('status','--short').decode(), 'manual_push':False,
'no_writeback_scope':'Disables C15/C23 status repairs only; brief generation/auto-commit and normal publication still run.'})
print(json.dumps({'owner_commit':owner_commit,'sync_return_code':result.returncode,
'brief_restored':True,'manual_push':False}))
finally:
# No broad checkout/reset and no suppression of a failed publication.
pass

View file

@ -0,0 +1,10 @@
{
"head": "dc042022f7c30671cf0a7605cf43ef6d96d04242",
"workplan_sha256": "3a3fc05528eb869204811d90d12ef5a3ac3b6d537b5b7a8cccbc15b6483a002f",
"brief_index": "100644 5e12ebd1d7606f88c606a5cc23b2c24d1afa8a3a 0\t.custodian-brief.md",
"brief_worktree_sha256": "009d2b150ad8f519818187298527da687946dc019bc35c72821170e7dd1942f8",
"work_records_sha256": "f713204e5c524f22e4a831fa480bf0e2018e206cd48f55fbf9d5472bfca104a5",
"staged_paths": [
".custodian-brief.md"
]
}

View file

@ -0,0 +1,22 @@
{
"schema": "access-engine.native-plan-review-artifacts.v1",
"work_item": "FLEX-WP-0024-T06",
"files": {
"access-native-plan-review.json": "5f49b9d703b3b0c050be8b45a97aab07b0b3af13e9ce56139f886e88a0dc740d",
"brief.preexisting.bytes": "009d2b150ad8f519818187298527da687946dc019bc35c72821170e7dd1942f8",
"execution-supplement.md": "2622cf68d33f5e5f4ede68655c250f70a8132ed03cb4b7c33bdbeec86100e25d",
"formatting-scanner-receipt.json": "443eeba3afc887384903dcefaa560c328eca2d57b470adc55832b4cc22b67a42",
"formatting-successor.json": "3769435ed8a8bc16893642a2e13cbb22feafa0b0f5fbdde74e835413618d69e3",
"integrate.py": "23619d448334e790d98f5972cefe9f51dc2dce98fecb359a266dfc6ed990fd44",
"integration-preimage.json": "3d96b83644ac41f082801a867329320ef1779fd622fb3a4ad96b4d6e72784af8",
"integration-receipt.json": "c019d3594131872d15c52e3de1620a796b0b03fd4612eed782ce12a374566a10",
"own-record-metadata.json": "758db57e818c1dcf312d3e25ceaccd09c848091f500eacb78f12db9f1a080c7b",
"own-record.patch": "d3eacdc45194913a2861503f0fc81332cfb414c5bece49ae8adf2dbc698eaabe",
"progress-receipt.json": "c1e2828d1dc169b164510c0c6c9ca9134876b2d44361e642e20da0dbef628cd7",
"sync.stderr.txt": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"sync.stdout.json": "1b0b0b7b02fd25fb21ceb7c1a30c70632221db1c73183c868db765e9915dc7e8",
"verification.json": "05e88b9c4362a9655be7ac19533fa3079558e7d2b57c446b90729a40cc88766f",
"verify.py": "ebeddcbdeb81d990999ac444f7f1c2e0d786a672b03234fc232092a6ea75835d"
},
"execution_authorized": false
}

View file

@ -0,0 +1,14 @@
{
"schema": "access-engine.owner-record-proposed-patch.v1",
"source_path": "/home/worsch/access-engine/workplans/FLEX-WP-0024-decision-envelope-authenticity.md",
"source_sha256": "3a3fc05528eb869204811d90d12ef5a3ac3b6d537b5b7a8cccbc15b6483a002f",
"patch_sha256": "d3eacdc45194913a2861503f0fc81332cfb414c5bece49ae8adf2dbc698eaabe",
"proposed_result_sha256": "cc94a493c23c16a3baa718b75ef5d48a596c6386c11534d50fd4839a6f0448f2",
"task_id": "FLEX-WP-0024-T06",
"status_before": "progress",
"status_after": "progress",
"integrated": true,
"instruction": "Report to coordinator before integration; after authorized integration run statehub fix-consistency and log progress.",
"canonical_commit": "d328a2601095686f74376d87791bff01e2a3a2a3",
"integration_receipt": "integration-receipt.json"
}

View file

@ -0,0 +1,24 @@
--- a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
+++ b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
@@ -325,3 +325,21 @@
Frozen rollout R1, Platform P2 and the package extraction2b/current05a remain
unchanged. T06 stays `progress`; actual stopped-fence verification and all native
admission gates remain open under the same existing task.
+
+
+2026-10-16 Access owner exact native plan review (Custodian
+`access-native-plan-review-next/`): approved bounded Access workload/Helm/outbox/
+stopped-only rollback plan for frozen rollout R2 manifest5be7a7f and Platform P4
+manifest9eec861, chartdc0420/image84c78e OCI09a83b/projection8ce375f. Offline
+verification checks every R2 manifest/source digest and reproduces both exact
+Helm render bytes. The mandatory execution supplement names actual repo/Helm/CI
+paths and excludes unrelated legacy rollback recipes, records the owner/founder
+manual-writer acknowledgement, supplies a verified stopped-revision rollback
+shape, and separates120-second stop/readiness gates from atomic rollback and
+the600-second recovery decision. No production source change was needed.
+Existing read-only preflight admits all7exact resources but still observes
+unsigned05a/revision4running, no installed stopped fence or held window. T06
+stays progress: actual founder entity/expiry/window/executor and five final
+external scope dispositions, custody/trust/caller, actual stopped Helm revision,
+PVC/PV Retain and native producer/consumer/restart/failure receipts remain open.
+No live mutation, credential access, new key, authority or protected effect follows.

View file

@ -0,0 +1,14 @@
{
"workplan_id": "90577acd-6910-548d-a13e-1dbfdfb8ed27",
"id": "79336b2d-260a-46b9-a74f-e2e6aaab1fb7",
"topic_id": null,
"task_id": "7f785b52-bf62-5884-8d34-670d70bc9023",
"decision_id": null,
"event_type": "note",
"summary": "Access owner bounded native plan review integrated at d328a2601095686f74376d87791bff01e2a3a2a3 under FLEX-WP-0024-T06. Exact frozen R2/P4 and source/Helm render checks pass; execution supplement records writer acknowledgement, stopped-only rollback and 120s/600s gates. Normal fix-consistency --no-writeback published commit and updated T06 description: 0 failures, 0 automation errors, 10 existing warnings. Preexisting staged brief index/worktree and dirty WORK-RECORDS bytes preserved exactly. No native authority, custody/trust/caller admission or cluster mutation. Evidence: Custodian access-native-plan-review-next/.",
"detail": null,
"author": "codex",
"session_id": null,
"created_at": "2026-10-16T19:16:32.489704Z",
"workstream_id": "90577acd-6910-548d-a13e-1dbfdfb8ed27"
}

View file

@ -0,0 +1,121 @@
{
"repo_slug": "access-engine",
"repo_path": "/home/worsch/access-engine",
"source_revision": "d328a2601095686f74376d87791bff01e2a3a2a3",
"source_revision_reason": null,
"issues": [
{
"severity": "WARN",
"check_id": "C-40",
"message": "blocker satisfied, task still wait: 'FLEX-WP-ADHOC-2026-10-03-T02' in workplan 'FLEX-WP-ADHOC-2026-10-03' \u2014 every depends_on target is terminal (WARDEN-WP-0039-T03)",
"file_path": "workplans/ADHOC-2026-10-03.md#FLEX-WP-ADHOC-2026-10-03-T02",
"db_id": "",
"file_value": "WARDEN-WP-0039-T03",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-40",
"message": "blocker satisfied, task still wait: 'FLEX-WP-ADHOC-2026-10-03-T02' in workplan 'FLEX-WP-ADHOC-2026-10-03' \u2014 decision 877c268b-2089-42f1-a811-d98f549c1cb2 is resolved",
"file_path": "workplans/ADHOC-2026-10-03.md#FLEX-WP-ADHOC-2026-10-03-T02",
"db_id": "",
"file_value": "877c268b-2089-42f1-a811-d98f549c1cb2",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-20",
"message": "Workplan dependency target 'NK-WP-0024' is not linked to State Hub",
"file_path": "workplans/FLEX-WP-0009-user-engine-production-policy-service.md",
"db_id": "",
"file_value": "NK-WP-0024",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-40",
"message": "blocker satisfied, task still wait: 'FLEX-WP-0020-T11' in workplan 'FLEX-WP-0020' \u2014 decision e071b34e-c4d4-49d0-824d-2c58bb0ae496 is resolved",
"file_path": "workplans/FLEX-WP-0020-repository-identity-migration.md#FLEX-WP-0020-T11",
"db_id": "",
"file_value": "e071b34e-c4d4-49d0-824d-2c58bb0ae496",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-22",
"message": "Task description drift 'FLEX-WP-0024-T06': file content differs from DB (file wins)",
"file_path": "workplans/FLEX-WP-0024-decision-envelope-authenticity.md#FLEX-WP-0024-T06",
"db_id": "7f785b52-bf62-5884-8d34-670d70bc9023",
"file_value": "Wait on Platform/OpenBao key custody, independent public-key and issuer/policy-pin\nadmission, exact image and existing-S",
"db_value": "Wait on Platform/OpenBao key custody, independent public-key and issuer/policy-pin\nadmission, exact image and existing-S",
"fixable": true
},
{
"severity": "WARN",
"check_id": "C-37",
"message": "Residual workplan 'flex-wp-0031' has no origin/origin_ref provenance (not invented)",
"file_path": "workplans/FLEX-WP-0031-decision-record-emission.md",
"db_id": "84f5d9fe-b4c9-584a-b964-efe3e48af095",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-39",
"message": "Human wait task 'FLEX-WP-0036-T01' in workplan 'FLEX-WP-0036' has needs_human: true without decision_id \u2014 unrouted human gate",
"file_path": "workplans/FLEX-WP-0036-runtime-rename-access-engine.md#FLEX-WP-0036-T01",
"db_id": "",
"file_value": "needs_human: true",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-39",
"message": "Human wait task 'FLEX-WP-0036-T04' in workplan 'FLEX-WP-0036' has needs_human: true without decision_id \u2014 unrouted human gate",
"file_path": "workplans/FLEX-WP-0036-runtime-rename-access-engine.md#FLEX-WP-0036-T04",
"db_id": "",
"file_value": "needs_human: true",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-28",
"message": "5 unread inbox message(s) older than 3 day(s): ops-warden:9eab1dc9, net-kingdom:31561723, secrets-engine:fd77c280, the-custodian:23172cb2, the-custodian:51e9eace",
"file_path": "",
"db_id": "",
"file_value": "",
"db_value": "",
"fixable": false
},
{
"severity": "WARN",
"check_id": "C-28",
"message": "2 unread message(s) lack thread_id for supersession tracking: the-custodian:23172cb2, the-custodian:51e9eace",
"file_path": "",
"db_id": "",
"file_value": "",
"db_value": "",
"fixable": false
}
],
"fixes_applied": [
"bindings: synced 38/38",
"C-17 cleared: pushed 1 backlogged commit(s)",
"C-22 fixed: task 7f785b52\u2026 description updated",
"push: pushed"
],
"summary": {
"fail": 0,
"automation_error": 0,
"warn": 10,
"info": 0
},
"result": "warn"
}

View file

@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Offline exact-source owner review verification. No cluster/credential access."""
import copy
import hashlib
import io
import json
from pathlib import Path
import subprocess
import tarfile
import tempfile
import yaml
HERE = Path(__file__).resolve().parent
WAVE = HERE.parent
ACCESS = Path('/home/worsch/access-engine')
CHART = 'dc042022f7c30671cf0a7605cf43ef6d96d04242'
IMAGE = '84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0'
def digest(data):
return hashlib.sha256(data).hexdigest()
def gitbytes(revision, path):
return subprocess.check_output(['git', '-C', str(ACCESS), 'show', f'{revision}:{path}'])
def write(name, data):
(HERE / name).write_text(json.dumps(data, indent=2) + '\n')
pins = {}
for rel in ['access-native-admission/rollout-r2/manifest.json',
'platform-signer-custody/native-adapter-r2/native-packet-p4/manifest.json',
'native-preflight-r1/live-preflight.json',
'native-preflight-r1/cluster-native-plan-review.json']:
pins[rel] = digest((WAVE / rel).read_bytes())
assert pins['access-native-admission/rollout-r2/manifest.json'] == '5be7a7f438624dcb35b7dbc96e7c9e5a8e2d5d1b1966cb4338aa5aecbe3a8b59'
assert pins['platform-signer-custody/native-adapter-r2/native-packet-p4/manifest.json'] == '9eec861838a8e9138f2c821cff3014998fbe74bb01447642bf50d44e8b3b7cb3'
rollout = WAVE / 'access-native-admission/rollout-r2'
manifest = json.loads((rollout / 'manifest.json').read_text())
for path, expected in manifest['files'].items():
assert 'sha256:' + digest((rollout / path).read_bytes()) == expected, path
evidence = json.loads((rollout / 'render-evidence.json').read_text())
for path, expected in evidence['source_files'].items():
assert 'sha256:' + digest(gitbytes(CHART, path)) == expected, path
if path.startswith('examples/'):
assert gitbytes(CHART, path) == gitbytes(IMAGE, path)
with tempfile.TemporaryDirectory(prefix='access-plan-review-') as temporary:
root = Path(temporary)
archive = subprocess.check_output(['git', '-C', str(ACCESS), 'archive', CHART])
with tarfile.open(fileobj=io.BytesIO(archive)) as tar:
tar.extractall(root, filter='data')
states = []
for phase in ['stopped', 'activation']:
command = ['helm', 'template', 'flex-auth-secrets-engine', str(root / 'charts/flex-auth'),
'--namespace', 'flex-auth', '-f', str(root / 'values/secrets-engine.yaml'),
'-f', str(rollout / f'{phase}-values.proposed.yaml')]
rendered = subprocess.check_output(command)
assert rendered == (rollout / f'{phase}-source-render.yaml').read_bytes()
states.append([x for x in yaml.safe_load_all(rendered) if x])
stopped, ready = states
normalized = copy.deepcopy(ready)
next(x for x in normalized if x['kind'] == 'Deployment')['spec']['replicas'] = 0
assert normalized == stopped
deployment = next(x for x in stopped if x['kind'] == 'Deployment')
assert deployment['spec']['strategy'] == {'type': 'Recreate'}
assert deployment['spec']['replicas'] == 0
assert not any(x['kind'] == 'Secret' for x in stopped)
pvc = next(x for x in stopped if x['kind'] == 'PersistentVolumeClaim')
assert pvc['metadata']['annotations']['helm.sh/resource-policy'] == 'keep'
assert pvc['spec']['resources']['requests']['storage'] == '1Gi'
paths = ['AGENTS.md', 'INTENT.md', 'SCOPE.md', '.forgejo/workflows/image.yaml',
'.forgejo/workflows/ci-smoke.yaml', '.github/workflows/ci.yml', 'Makefile',
'deploy/README.md', 'railiance/app.toml', 'tools/publish-runtime-images.sh',
'internal/emission/outbox.go', 'internal/emission/lock_linux.go',
'internal/emission/lock_other.go', 'internal/emission/delivery.go',
'internal/decision/engine.go', 'cmd/access-engine/main.go']
source_pins = {p: digest(gitbytes(CHART, p)) for p in paths}
source_pins['workplans/FLEX-WP-0024-decision-envelope-authenticity.md'] = digest(
(ACCESS / 'workplans/FLEX-WP-0024-decision-envelope-authenticity.md').read_bytes())
image_source = {p: digest(gitbytes(IMAGE, p)) for p in paths if p.startswith(('internal/', 'cmd/'))}
for p, sha in image_source.items():
assert source_pins[p] == sha, p
for p in ['.forgejo/workflows/image.yaml', '.forgejo/workflows/ci-smoke.yaml', '.github/workflows/ci.yml']:
data = gitbytes(CHART, p).decode()
assert 'helm upgrade' not in data and 'kubectl apply' not in data
preflight = json.loads((WAVE / 'native-preflight-r1/live-preflight.json').read_text())
observations = preflight['observations']
writer_observations = {k: observations[k] for k in ['hpa', 'jobs', 'cronjobs', 'daemonsets', 'statefulsets']}
assert all(x['ok'] and not x['rows'] for x in writer_observations.values())
write('verification.json', {
'schema': 'access-engine.native-plan-offline-verification.v1',
'frozen_input_sha256': pins, 'chart_revision': CHART, 'image_revision': IMAGE,
'reviewed_source_sha256': {p: 'sha256:' + value for p, value in source_pins.items()},
'image_runtime_source_sha256': {p: 'sha256:' + value for p, value in image_source.items()},
'frozen_manifest_file_hashes_verified': True, 'chart_source_hashes_verified': True,
'actual_exact_helm_renders_equal_frozen_bytes': True,
'stopped_ready_only_replicas_differ': True, 'runtime_sources_identical_chart_and_image': True,
'reviewed_repo_ci_has_no_helm_upgrade_or_kubectl_apply': True,
'dated_namespace_competing_controller_observations': writer_observations,
'observation_time': preflight['observed_at'], 'live_execution': False,
'no_credential_or_kubeconfig_access': True})
print('Verified frozen R2/P4 hashes, exact source files and two byte-identical Helm renders; no native access.')

View file

@ -0,0 +1,19 @@
#!/usr/bin/env python3
"""Allowlisted metadata only: never query Secret objects, logs, env or exec."""
import datetime,json,subprocess
from pathlib import Path
out=Path(__file__).resolve().parent
queries={
'nodes':['kubectl','get','nodes','-o','jsonpath={range .items[*]}{.metadata.name}{" "}{.status.addresses}{"\\n"}{end}'],
'deployment':['kubectl','-n','flex-auth','get','deployment','flex-auth-secrets-engine','-o','jsonpath={.metadata.name}{"\\n"}{.metadata.labels}{"\\n"}{.metadata.annotations.meta\\.helm\\.sh/release-name}{"\\n"}{.metadata.annotations.meta\\.helm\\.sh/release-namespace}{"\\n"}{.spec.template.spec.containers[0].image}{"\\n"}{.spec.template.spec.containers[0].args}{"\\n"}{.spec.template.spec.serviceAccountName}{"\\n"}{.spec.template.spec.containers[0].volumeMounts}{"\\n"}{.metadata.generation}{" "}{.status.observedGeneration}{" "}{.status.availableReplicas}{"\\n"}'],
'pods':['kubectl','-n','flex-auth','get','pods','-l','app.kubernetes.io/name=flex-auth-secrets-engine','-o','jsonpath={range .items[*]}{.metadata.name}{" "}{.status.phase}{" "}{.spec.nodeName}{" "}{.status.containerStatuses[0].imageID}{"\\n"}{end}'],
'service':['kubectl','-n','flex-auth','get','service','flex-auth-secrets-engine','-o','jsonpath={.metadata.name}{" "}{.spec.clusterIP}{" "}{.spec.selector}{" "}{.spec.ports}{"\\n"}'],
'helm_release':['helm','list','-n','flex-auth','--filter','^flex-auth-secrets-engine$','-o','json'],
'custody_route':['warden','route','show','flex-auth-decision-envelope-signing-key','--json']}
results={}
for name,args in queries.items():
r=subprocess.run(args,capture_output=True,text=True,timeout=20)
# Never expose raw unexpected CLI errors.
results[name]={'command':args,'exit_code':r.returncode,'output':r.stdout if r.returncode==0 else None,'stderr_present':bool(r.stderr)}
(out/'live-metadata-r1.json').write_text(json.dumps({'schema':'access-engine.readonly-native-metadata.v1','observed_at_utc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'boundary':'metadata reads only; no Secret objects, credential requests, tokens, exec, logs or writes','queries':results},indent=2)+'\n')
print({name:record['exit_code'] for name,record in results.items()})

View file

@ -0,0 +1,92 @@
{
"schema": "access-engine.readonly-native-metadata.v1",
"observed_at_utc": "2026-10-16T07:24:16.644831+00:00",
"boundary": "metadata reads only; no Secret objects, credential requests, tokens, exec, logs or writes",
"queries": {
"nodes": {
"command": [
"kubectl",
"get",
"nodes",
"-o",
"jsonpath={range .items[*]}{.metadata.name}{\" \"}{.status.addresses}{\"\\n\"}{end}"
],
"exit_code": 0,
"output": "239.62.205.92.host.secureserver.net [{\"address\":\"92.205.62.239\",\"type\":\"InternalIP\"},{\"address\":\"2a00:1169:111:6d70::\",\"type\":\"InternalIP\"},{\"address\":\"239.62.205.92.host.secureserver.net\",\"type\":\"Hostname\"}]\n",
"stderr_present": false
},
"deployment": {
"command": [
"kubectl",
"-n",
"flex-auth",
"get",
"deployment",
"flex-auth-secrets-engine",
"-o",
"jsonpath={.metadata.name}{\"\\n\"}{.metadata.labels}{\"\\n\"}{.metadata.annotations.meta\\.helm\\.sh/release-name}{\"\\n\"}{.metadata.annotations.meta\\.helm\\.sh/release-namespace}{\"\\n\"}{.spec.template.spec.containers[0].image}{\"\\n\"}{.spec.template.spec.containers[0].args}{\"\\n\"}{.spec.template.spec.serviceAccountName}{\"\\n\"}{.spec.template.spec.containers[0].volumeMounts}{\"\\n\"}{.metadata.generation}{\" \"}{.status.observedGeneration}{\" \"}{.status.availableReplicas}{\"\\n\"}"
],
"exit_code": 0,
"output": "flex-auth-secrets-engine\n{\"app.kubernetes.io/instance\":\"flex-auth-secrets-engine\",\"app.kubernetes.io/managed-by\":\"Helm\",\"app.kubernetes.io/name\":\"flex-auth-secrets-engine\",\"app.kubernetes.io/part-of\":\"flex-auth\"}\nflex-auth-secrets-engine\nflex-auth\nforgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd\n[\"serve\",\"--addr\",\"0.0.0.0:8080\",\"--registry\",\"/opt/flex-auth/examples/secrets-engine/registry_snapshot.json\",\"--policy\",\"/opt/flex-auth/examples/secrets-engine/policy_package.md\",\"--caller-auth-mode\",\"enforce\",\"--caller-kubernetes-url\",\"https://10.43.0.1\",\"--caller-binding\",\"secrets-engine=system:serviceaccount:secrets-engine:secrets-engine\"]\nflex-auth-secrets-engine\n[{\"mountPath\":\"/var/run/secrets/flex-auth-reviewer\",\"name\":\"flex-auth-reviewer\",\"readOnly\":true}]\n4 4 1\n",
"stderr_present": false
},
"pods": {
"command": [
"kubectl",
"-n",
"flex-auth",
"get",
"pods",
"-l",
"app.kubernetes.io/name=flex-auth-secrets-engine",
"-o",
"jsonpath={range .items[*]}{.metadata.name}{\" \"}{.status.phase}{\" \"}{.spec.nodeName}{\" \"}{.status.containerStatuses[0].imageID}{\"\\n\"}{end}"
],
"exit_code": 0,
"output": "flex-auth-secrets-engine-6467b96c6f-ncl5t Running 239.62.205.92.host.secureserver.net forgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd\n",
"stderr_present": false
},
"service": {
"command": [
"kubectl",
"-n",
"flex-auth",
"get",
"service",
"flex-auth-secrets-engine",
"-o",
"jsonpath={.metadata.name}{\" \"}{.spec.clusterIP}{\" \"}{.spec.selector}{\" \"}{.spec.ports}{\"\\n\"}"
],
"exit_code": 0,
"output": "flex-auth-secrets-engine 10.43.40.221 {\"app.kubernetes.io/name\":\"flex-auth-secrets-engine\"} [{\"name\":\"http\",\"port\":8080,\"protocol\":\"TCP\",\"targetPort\":\"http\"}]\n",
"stderr_present": false
},
"helm_release": {
"command": [
"helm",
"list",
"-n",
"flex-auth",
"--filter",
"^flex-auth-secrets-engine$",
"-o",
"json"
],
"exit_code": 0,
"output": "[{\"name\":\"flex-auth-secrets-engine\",\"namespace\":\"flex-auth\",\"revision\":\"4\",\"updated\":\"2026-09-14 00:46:30.759520357 +0200 CEST\",\"status\":\"deployed\",\"chart\":\"flex-auth-0.2.0\",\"app_version\":\"0.1.0\"}]\n",
"stderr_present": false
},
"custody_route": {
"command": [
"warden",
"route",
"show",
"flex-auth-decision-envelope-signing-key",
"--json"
],
"exit_code": 0,
"output": "{\n \"id\": \"flex-auth-decision-envelope-signing-key\",\n \"title\": \"Ed25519 key that signs flex-auth decision envelopes\",\n \"owner_repo\": \"railiance-platform\",\n \"subsystem\": \"OpenBao + flex-auth\",\n \"warden_executes\": false,\n \"warden_role\": \"route\",\n \"exec_capable\": false,\n \"resolvable\": false,\n \"wiki_ref\": \"wiki/playbooks/flex-auth-decision-envelope-signing-key.md#worker-checklist\",\n \"canon_ref\": \"flex-auth/docs/decision-envelope-signature.md\",\n \"reviewed\": \"2026-09-14\",\n \"status\": \"draft\",\n \"risk\": \"high\",\n \"high_risk\": true,\n \"workload_ref\": {\n \"applicability\": \"applicable\",\n \"rapp_id\": null,\n \"name\": null,\n \"deployable\": null,\n \"declaration_ref\": null,\n \"reason\": null,\n \"unknown_reason\": \"flex-auth has not published an authoritative workload identity declaration for envelope signing.\",\n \"resolution\": \"unknown\"\n },\n \"has_rotation\": false,\n \"delegation\": {\n \"mode\": \"interim\",\n \"intended_owner\": \"railiance-platform\",\n \"blocked_on\": \"Shape is chosen in flex-auth (FLEX-WP-0024-T02). OpenBao KV path, Kubernetes auth role, and paste_once_provision of the private seed are not built. Testdata key in flex-auth is not this lane.\",\n \"reviewed\": \"2026-09-14\",\n \"verified\": \"unverified\",\n \"is_verified\": false,\n \"implicit\": false\n },\n \"need_keywords\": [\n \"flex-auth\",\n \"decision\",\n \"envelope\",\n \"signing\",\n \"signature\",\n \"ed25519\",\n \"responder\",\n \"authenticity\",\n \"FLEX-WP-0024\"\n ],\n \"next_action\": \"next action on `railiance-platform` \\u2014 see `wiki/playbooks/flex-auth-decision-envelope-signing-key.md#worker-checklist`\"\n}\n",
"stderr_present": false
}
}
}

View file

@ -0,0 +1,179 @@
{
"schema": "access-engine.native-admission-preparation.v1",
"proposal_id": "FLEX-WP-0024-native-signer-20261016",
"revision": "R1-preparation",
"status": "owner preparation complete; not executable until named missing inputs are supplied and exact revision reviewed",
"authority": "Read-only source/metadata preparation authorized by coordinator. No trust admission, credentials, key generation, live changes, policy enablement or foreign approval. Supervision summary grants no authority; this scope remains supervised.",
"scope": {
"beneficiary": "FLEX-WP-0024-T06",
"coordination_driver": "CUST-WP-0080",
"initial_target": "Existing flex-auth/flex-auth-secrets-engine lifecycle decision pin and Secrets Engine consumer only",
"purpose": "Authenticate the existing decision producer and prove admitted consumer verification; preserve execute-ccr DENY until separately reviewed source joins and exact operation admission exist",
"explicit_pilot": "Existing secrets-engine.task-execution.service-pilot source, SECRETS-WP-0013/RPF-WP-0035-T12; no new broker or grant"
},
"current_live": {
"evidence": "live-metadata-r1.json",
"cluster_node_ip": "92.205.62.239",
"namespace": "flex-auth",
"release": "flex-auth-secrets-engine",
"release_revision": 4,
"chart": "flex-auth-0.2.0",
"deployment": "flex-auth-secrets-engine",
"pod": "flex-auth-secrets-engine-6467b96c6f-ncl5t",
"image": "forgejo.coulomb.social/coulomb/flex-auth@sha256:05a03a8790c2210c48ea92391441c77ddf640d0cd32f5ec09838f5393171fcbd",
"source_association": {
"historical_owner_record": "values/secrets-engine.yaml associates that digest with dd8dd517438b876fdadf27770e3f7e7f55ea69cf",
"limit": "Live image metadata proves digest, not source provenance. No fresh CI/provenance receipt was inspected; do not infer current signer source is deployed."
},
"registry": "/opt/flex-auth/examples/secrets-engine/registry_snapshot.json",
"policy": "/opt/flex-auth/examples/secrets-engine/policy_package.md",
"service": "flex-auth-secrets-engine.flex-auth.svc.cluster.local.:8080",
"service_cluster_ip": "10.43.40.221",
"caller_auth_mode": "enforce",
"caller_kubernetes_url": "https://10.43.0.1",
"caller_binding": "secrets-engine=system:serviceaccount:secrets-engine:secrets-engine",
"reviewer_service_account": "flex-auth/flex-auth-secrets-engine",
"signer_args_or_mounts_present": false,
"observed_available_replicas": 1
},
"source_delivery": {
"revision": "ec21518e25df2a58444f19ff3413ce0b83383d3c",
"signing_integration": "ec21518e25df2a58444f19ff3413ce0b83383d3c",
"chart": "charts/flex-auth retained runtime, updated at exact source revision",
"policy_pins_offline_owner_function": {
"examples/secrets-engine-service-task-execution/policy_package.md": {
"digest": "sha256:b034dc8ad208889113ce475d33df1589f5b84cb817bbb361d10638e23e394a6e",
"id": "secrets-engine.task-execution.service-pilot",
"valid": true,
"version": "v1"
},
"examples/secrets-engine/policy_package.md": {
"digest": "sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4",
"id": "secrets-engine.catalog-lane.lifecycle",
"valid": true,
"version": "v2"
}
},
"evaluator": "flex-auth/local",
"mode": "standalone",
"image_build": "Owner Forgejo CI legacy package publishing path .forgejo/workflows/image.yaml and tools/publish-runtime-images.sh; no workstation image build",
"candidate_image_digest": null,
"compatibility": "Containerfile ENTRYPOINT access-engine and retains both /opt/access-engine/examples and /opt/flex-auth/examples paths; avoid combining signer rollout with namespace/identity rename",
"public_package_rename": "Not needed; existing admitted legacy OCI package remains sufficient"
},
"custody_and_independent_trust": {
"exact_route": "warden route show flex-auth-decision-envelope-signing-key --json",
"route_state": "draft, nonresolvable; exact entry exists even though fuzzy find exits1 with adjacent matches",
"owner": "railiance-platform/OpenBao",
"ops_warden_executes": false,
"proposed_source_path": "platform/workloads/flex-auth/envelope-signing",
"proposed_fields": [
"ED25519_PRIVATE_KEY",
"ED25519_PUBLIC_KEY",
"kid"
],
"private_encoding_required_by_integrated_source": "Unpadded strict base64url 32-byte seed file; mount0440 accepted, group-write/world bits refused; key read once at startup",
"missing": [
"Authoritative signer workload identity declaration",
"Platform custody lane, KV/auth role and owner-reviewed projection/Secret mechanism",
"Owner-selected production kid and public key/fingerprint with independently authenticated custody receipt",
"Named existing Secret/key and exact read-only projection path",
"Public admission receipt for each consumer and intended policy package/version/digest",
"Native rotation/revocation/retained audit verification plan"
],
"public_key": null,
"kid": null,
"fingerprint": null,
"custody_receipt": null,
"bootstrap_rule": "Receive reviewed public material and policy pins independently from custody/owner. Do not trust responder /v1/keys or infer authenticity from policy digest; /v1/keys is not implemented. Do not use fixture keys.",
"consumer_format": "secrets-engine.pdp-trust.v1: exact evaluator/mode/policy_package/policy_version/policy_package_digest and kid->public key map; operator-controlled absolute regular file, no group/world write. Installed reviewed Go helper required."
},
"enforced_caller_and_remaining_join": {
"existing_contract": "TokenReview authenticates bearer against configured Kubernetes API/CA and maps returned principal exactly to resource.system. Caller provenance is signed. The consumer token is not the PDP reviewer token.",
"audience_caution": "Live args omit explicit audience (legacy binary defaults flex-auth). Current source has bounded flex-auth/access-engine audience compatibility; ServicePilotInputs currently requires record.Audience exactly flex-auth. Preserve explicit flex-auth on first pilot and test alternate spelling refusal before any broader admission; do not infer compatibility grants subject authority.",
"new_proposed_mapping": {
"version": "proposed-service-task-caller/v1",
"transport_principal": "system:serviceaccount:secrets-engine:secrets-engine",
"subject_id": "secrets-engine",
"subject_type": "service",
"tenant": "tenant:platform"
},
"public_binding_rule": "service_actor_binding is public canonical request metadata, not authentication. ServicePilotInputs.Prepare must independently compare actual reviewed transport, fixed subject/type/tenant and exact binding; reject extras and unverified/delegation flags. Deadline must be positive and no later than reviewed transport expiry.",
"current_gap": "ServicePilotInputs is only source input preparation and tests; cmd/access-engine /v1/check routes Authorize(resource.system) directly to Engine.Check(request). No Prepare call or trusted service input reaches engine. Existing system mapping alone cannot authenticate arbitrary request subjects.",
"other_required_joins": [
"Authenticate served Approval origin and verified human approver as separate principal",
"Join exact approval-free/request/submitted digests to actual compiled execution descriptor and unchanged canonical request",
"Retain trusted provenance outside caller-forgeable request flags; no field named verified/owner_admission can enable policy",
"Downstream consumer/Platform independently join exact consumed approval/decision/request, journal admission, full source/state/target/body/deadline/use constraints"
],
"future_api_rule": "Integrate through existing owner decision route and provider adapters; no second PDP or policy evaluator in consumer"
},
"closed_policy_gates": {
"legacy_lifecycle": "secrets-engine.catalog-lane.lifecycle v2 supports twelve original lifecycle actions, denies execute-ccr unknown_action; do not relabel execute as apply",
"service_pilot": "secrets-engine.task-execution.service-pilot v1 literal runtime_admission := false; normal result service_execution_integration_unadmitted",
"human_variant": "Existing separate human task package remains unchanged and unadmitted",
"local_positive_evidence": "Independent local stack used a separately named synthetic policy. It does not establish service-pilot runtime permission.",
"packaging_caution": "Engine loads one policy package per process. Swapping existing lifecycle pin to isolated task-only package would remove legacy actions. No such swap is authorized. Later packaging choice must preserve existing lifecycle behavior or use owner-reviewed isolated current-pilot deployment; do not quietly broaden or combine packages."
},
"minimal_next_changes": [
{
"stage": "source-only preparation available now",
"owner": "Access Engine",
"change": "Publish authoritative signer workload identity/reference through existing estate declaration; define exact native tests and explicit retained-runtime audience; attach custody lane dependency to current coordination driver. No credential required."
},
{
"stage": "signer-only native trial after missing inputs and exact admission",
"owner": "Access Engine + Platform + Secrets",
"change": "CI-built exact ec21518e successor image on same retained flex-auth-secrets-engine release; activate existing envelopeSigning block with admitted kid/Secret/key read-only mount. Keep lifecycle package, caller-auth enforce, exact binding and task DENY. Install reviewed consumer bundle and independent trust. Pin source/image/helper/trust digests and use owner-bound literal loopback tunnel."
},
{
"stage": "later bounded service execution source successor",
"owner": "Access Engine with Approval/Secrets/Platform owner reviews",
"change": "Wire actual authenticated service input and independently authenticated provider joins into existing decision route, characterize real negative cases, and replace structural false gate only with a reviewed complete eligibility path. No enable flag or request boolean. Current pilot false gate remains until this separate implementation and admission completes."
}
],
"required_tests_before_native_acceptance": [
"CI source-to-OCI digest provenance; rendered manifest/image/args/policy unchanged apart from explicit signer and candidate image; CPU headroom, exact client diff/server dry-run under owner deployment path before any apply.",
"Native reviewer CA/audience/RBAC provenance and projected token rotation; correct service passes, foreign/missing/expired token, wrong resource.system/subject/type/tenant, unknown transport expiry, public binding spoof/extra fields and deadline beyond transport expiry refuse.",
"Actual admitted producer returns signed allow for existing allowed lifecycle action and signed deny for forbidden action; exact consumer key/policy pins, request digest and lifetime validation; forged/altered/wrong-kid/unsigned/wrong-policy/expired refuse.",
"Current service-pilot still returns signed DENY before consume/effect; caller flags cannot flip it. Legacy lifecycle actions unchanged; no approval consumed in mere signer probe.",
"Returned signed decision equals retained original source record; require native durable recording/custody path explicitly (live deployment currently has no --log/outbox flags). Local unit/local JSONL parity is not native independent audit custody.",
"Admit successor public key first; prove successor verification; roll producer seed/kid; keep old public key only through maximum outstanding allow TTL plus bounded uncertainty; remove and prove old signature refusal. Emergency revoked keys remove immediately.",
"Exact installed Secrets wheel/helper provenance, target architecture, private path/mode admission and second clean install; no request-time compile or bundled test signer.",
"For later task activation separately: actual served claim/verified human approver provenance, exact request/descriptor and consumption triplet, one-shot journal, partial crash/unknown recovery, six fixed backend calls, use/time ceilings/readback/revoke/cleanup with independent audit receipts."
],
"rollback_and_abort": {
"abort_owner": "Access Engine deployment owner for PDP rollout; Platform for signer custody; Secrets owner for consumer enforcement. Named human action identity must be sealed in executable revision.",
"abort_conditions": [
"Any missing independent public admission or candidate artifact digest",
"Headroom insufficient, source/policy drift, unexpected caller/pin acceptance, unsigned response accepted, unexpected task allow, custody or record uncertainty"
],
"signer_rollout_failure": "Stop consumer protected operations; restore exact prior producer deployment revision only as availability/diagnostic rollback. Enforcing consumer remains fail-closed against unsigned responses; never enable unsigned fallback or reuse revoked key.",
"policy_join_failure": "Retain/restore original hard DENY current service-pilot. Any unknown protected effect requires owner reconciliation; do not auto-retry.",
"key_rotation": "Retain original failure/refinement and key-custody recovery receipts; no rollback to revoked/compromised predecessor."
},
"missing_execution_inputs": {
"candidate_ci_image_digest": null,
"existing_custody_secret_name": null,
"secret_key_field": null,
"admitted_kid_and_public_fingerprint": null,
"public_custody_admission_receipt": null,
"consumer_exact_installed_bundle_digest": null,
"native_durable_recording_and_audit_path": null,
"exact_owner_execution_revision_approval": null,
"native_headroom_diff_dryrun_receipts": null
},
"cost_risk": {
"preparation_incremental_spend_eur": 0,
"basis": "Read-only existing metadata and local source validation only; no paid resource provision or live mutation.",
"future_activation_cost_bound_eur": null,
"future_activation_risk_bound_eur": null,
"required_before_privilege": "Supervising owner must estimate and admit rollout/resource/custody risk for exact executable revision. Missing estimates do not mean free or authorized."
},
"work_record_correction": {
"existing_task": "FLEX-WP-0024-T06",
"recommended_status": "progress",
"reason": "Source owner still has actionable preparation, declaration and packaging work. A broad unqualified wait on custody plus consumer acceptance hides owner work and risks a dependency cycle with consumer T03. Native execution is genuinely blocked by draft custody route and missing public admission; record those as explicit stage gates without inventing a Platform task or approval.",
"coordination": "CUST-WP-0080 already coordinates Access, Secrets, Platform and Approval. Root should attach/retain named custody commitment through the existing project driver; no new workplan, worker wake or foreign task patch here."
}
}

View file

@ -0,0 +1 @@
{"examples/secrets-engine-service-task-execution/policy_package.md":{"digest":"sha256:b034dc8ad208889113ce475d33df1589f5b84cb817bbb361d10638e23e394a6e","id":"secrets-engine.task-execution.service-pilot","valid":true,"version":"v1"},"examples/secrets-engine/policy_package.md":{"digest":"sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4","id":"secrets-engine.catalog-lane.lifecycle","valid":true,"version":"v2"}}

View file

@ -0,0 +1,36 @@
diff --git a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
--- a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
+++ b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
@@ -184,7 +184,7 @@
```task
id: FLEX-WP-0024-T06
-status: wait
+status: progress
priority: high
```
@@ -197,3 +197,23 @@
source review. This task owns the producer-side native gate; SECRETS-WP-0014 owns
consumer admission/enforcement. The old fixture completion never established this
gate, so the workplan is reopened rather than claiming it already passed.
+
+
+2026-10-16 owner native preparation: T06 is `progress` because Access still owns
+signer workload declaration, artifact/provenance and source/provider-join preparation;
+no unqualified cross-owner wait is claimed. Read-only actual release metadata and
+bounded proposal are in the Custodian
+`docs/changes/netkingdom-target-2026-10-16/access-native-preparation/`.
+The real Platform custody route `flex-auth-decision-envelope-signing-key` exists but
+is draft/nonresolvable; no production kid/public fingerprint, Secret projection or
+custody receipt is admitted. Coordinate that named commitment through CUST-WP-0080
+instead of inventing a Platform task/approval. SECRETS-WP-0014-T03 owns installed
+consumer native verification and must not form a circular blocking dependency.
+
+The first proposed native trial only signs the unchanged lifecycle package on the
+existing retained release; `execute-ccr` remains denied. Current service-pilot
+`runtime_admission := false` remains mandatory: the source ServicePilotInputs is
+not yet wired into HTTP, and public service_actor_binding never authenticates the
+caller. Later activation needs a complete reviewed provider join, exact policy and
+operation admission, artifact digests, owner execution/rollback proposal and native
+receipts. Preparation/local synthetic success grants none of those permissions.

View file

@ -0,0 +1,766 @@
diff --git a/cmd/access-engine/main.go b/cmd/access-engine/main.go
--- a/cmd/access-engine/main.go
+++ b/cmd/access-engine/main.go
@@ -22,6 +22,7 @@
"github.com/netkingdom/access-engine/internal/emission"
"github.com/netkingdom/access-engine/internal/policy"
"github.com/netkingdom/access-engine/internal/registry"
+ "github.com/netkingdom/access-engine/internal/taskexecution"
"github.com/netkingdom/access-engine/pkg/api"
)
@@ -397,7 +398,11 @@
return fail(stderr, err)
}
- mux := newServeMuxWithCallerAuth(engine, authenticator)
+ serviceInputs, err := buildServicePilotInputs(engine, callerauth.Mode(*callerAuthMode), *callerAudience, *callerKubernetesURL, *callerReviewerTokenFile, *callerCAFile, callerBindings.StringMap())
+ if err != nil {
+ return fail(stderr, err)
+ }
+ mux := newServeMuxWithServicePilotInputs(engine, authenticator, serviceInputs)
if outbox != nil {
mountEmission(mux, outbox)
}
@@ -448,6 +453,10 @@
}
func newServeMuxWithCallerAuth(engine *decisioncore.Engine, authenticator *callerauth.Authenticator) *http.ServeMux {
+ return newServeMuxWithServicePilotInputs(engine, authenticator, nil)
+}
+
+func newServeMuxWithServicePilotInputs(engine *decisioncore.Engine, authenticator *callerauth.Authenticator, serviceInputs *taskexecution.ServicePilotInputs) *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("content-type", "application/json")
@@ -458,6 +467,25 @@
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
+ if engine.IsServicePilot() {
+ if serviceInputs == nil {
+ http.Error(w, "service input unavailable", http.StatusServiceUnavailable)
+ return
+ }
+ request, err := taskexecution.DecodeServiceRequest(r.Body)
+ if err != nil || request.Purpose != "" {
+ http.Error(w, "invalid service task request", http.StatusBadRequest)
+ return
+ }
+ input, err := serviceInputs.Prepare(r.Context(), r.Header.Get("Authorization"), request, time.Now().UTC())
+ if err != nil {
+ http.Error(w, "service task input refused", http.StatusForbidden)
+ return
+ }
+ decision, err := engine.CheckServicePilot(r.Context(), input)
+ writeHTTP(w, decision, err)
+ return
+ }
var request api.CheckRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -478,6 +506,10 @@
writeHTTP(w, decision, err)
})
mux.HandleFunc("/v1/batch_check", func(w http.ResponseWriter, r *http.Request) {
+ if engine.IsServicePilot() {
+ http.Error(w, "service task batch unavailable", http.StatusForbidden)
+ return
+ }
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
@@ -500,6 +532,22 @@
writeHTTP(w, decisions, err)
})
return mux
+}
+
+// Only the existing exact service pilot package selects this source path.
+// This is not an eligibility toggle; successful preparation still only DENYs.
+func buildServicePilotInputs(engine *decisioncore.Engine, mode callerauth.Mode, audience, kubernetesURL, reviewerTokenFile, caFile string, bindings map[string]string) (*taskexecution.ServicePilotInputs, error) {
+ if !engine.IsServicePilot() {
+ return nil, nil
+ }
+ if mode != callerauth.ModeEnforce || audience != "flex-auth" || len(bindings) != 1 || bindings["secrets-engine"] != taskexecution.ServicePilotPrincipal {
+ return nil, fmt.Errorf("service pilot requires exact enforced retained caller mapping")
+ }
+ reviewer, err := callerauth.NewKubernetesTokenReviewer(kubernetesURL, audience, reviewerTokenFile, caFile)
+ if err != nil {
+ return nil, err
+ }
+ return taskexecution.NewServicePilotInputs(reviewer)
}
func buildCallerAuthenticator(mode callerauth.Mode, audience, kubernetesURL, reviewerTokenFile, caFile string, bindings map[string]string, stderr io.Writer) (*callerauth.Authenticator, error) {
@@ -615,7 +663,7 @@
func writeHTTP(w http.ResponseWriter, value any, err error) {
w.Header().Set("content-type", "application/json")
- if errors.Is(err, decisioncore.ErrRecordNotCommitted) {
+ if errors.Is(err, decisioncore.ErrRecordNotCommitted) || errors.Is(err, decisioncore.ErrServicePilotUnavailable) {
// Unavailable evaluation is not an authorization verdict (GH-DEC-2026-024).
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
diff --git a/cmd/access-engine/service_pilot_http_test.go b/cmd/access-engine/service_pilot_http_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/access-engine/service_pilot_http_test.go
@@ -0,0 +1,376 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "crypto/ed25519"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "io"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "github.com/netkingdom/access-engine/internal/audit"
+ "github.com/netkingdom/access-engine/internal/callerauth"
+ "github.com/netkingdom/access-engine/internal/decision"
+ "github.com/netkingdom/access-engine/internal/policy"
+ "github.com/netkingdom/access-engine/internal/registry"
+ "github.com/netkingdom/access-engine/internal/sign"
+ "github.com/netkingdom/access-engine/internal/taskexecution"
+ "github.com/netkingdom/access-engine/pkg/api"
+)
+
+func pilotRequest(now time.Time) api.CheckRequest {
+ return api.CheckRequest{ID: "source-only-service-http", Tenant: "tenant:platform", Subject: api.SubjectRef{ID: "secrets-engine", Type: "service"}, Action: "execute-ccr", Resource: api.ResourceRef{ID: "catalog:synthetic", Type: "secret-catalog-lane", System: "secrets-engine", Attributes: map[string]any{"stage": "prod"}}, Context: map[string]any{"human_control": true, "service_actor_binding": taskexecution.DeclaredServiceBinding(), "execution_contract": map[string]any{"contract_version": "SECRETS-EXEC-20261011-v1", "operation": "metadata-apply-ccr", "execution_actor": "service:secrets-engine", "operation_deadline": now.Add(30 * time.Second).Format(time.RFC3339Nano)}, "approval": map[string]any{"caller_claim": "unchanged and untrusted in B1"}}}
+}
+func pilotEngine(t *testing.T) *decision.Engine {
+ t.Helper()
+ p, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ engine, err := decision.NewEngine(registry.NewStore(), p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return engine
+}
+func pilotToken(kind string, now time.Time) string {
+ payload := map[string]any{"case": kind, "exp": now.Add(2 * time.Minute).Unix()}
+ if kind == "expired" {
+ payload["exp"] = now.Add(-time.Second).Unix()
+ }
+ if kind == "no-expiry" {
+ delete(payload, "exp")
+ }
+ raw, _ := json.Marshal(payload)
+ return "synthetic." + base64.RawURLEncoding.EncodeToString(raw) + ".not-a-real-token"
+}
+func TestServicePilotRealHTTPAndTokenReviewSignedDeny(t *testing.T) {
+ now := time.Now().UTC()
+ var reviews atomic.Int64
+ reviewer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ reviews.Add(1)
+ if r.URL.Path != "/apis/authentication.k8s.io/v1/tokenreviews" || r.Header.Get("Authorization") != "Bearer synthetic-reviewer" {
+ t.Error("unexpected reviewer route/auth")
+ w.WriteHeader(403)
+ return
+ }
+ var body struct {
+ Spec struct {
+ Token string `json:"token"`
+ Audiences []string `json:"audiences"`
+ } `json:"spec"`
+ }
+ if json.NewDecoder(r.Body).Decode(&body) != nil {
+ w.WriteHeader(400)
+ return
+ }
+ if !reflect.DeepEqual(body.Spec.Audiences, []string{"flex-auth", "access-engine"}) {
+ t.Error("unexpected bounded runtime audience request")
+ }
+ pieces := strings.Split(body.Spec.Token, ".")
+ raw, _ := base64.RawURLEncoding.DecodeString(pieces[1])
+ var claims map[string]any
+ json.Unmarshal(raw, &claims)
+ principal := taskexecution.ServicePilotPrincipal
+ audiences := []string{"flex-auth"}
+ authenticated := true
+ switch claims["case"] {
+ case "foreign":
+ principal = "system:serviceaccount:other:other"
+ case "alternate-audience":
+ audiences = []string{"access-engine"}
+ case "wrong-audience":
+ audiences = []string{"openbao"}
+ case "unauthenticated":
+ authenticated = false
+ }
+ json.NewEncoder(w).Encode(map[string]any{"status": map[string]any{"authenticated": authenticated, "audiences": audiences, "user": map[string]any{"username": principal}}})
+ }))
+ defer reviewer.Close()
+ folder := t.TempDir()
+ ca := filepath.Join(folder, "ca.crt")
+ reviewToken := filepath.Join(folder, "reviewer-token")
+ os.WriteFile(ca, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: reviewer.Certificate().Raw}), 0600)
+ os.WriteFile(reviewToken, []byte("synthetic-reviewer"), 0600)
+ engine := pilotEngine(t)
+ private := ed25519.NewKeyFromSeed(make([]byte, 32))
+ if engine.SetEnvelopeSigningKey(private, "synthetic-b1") != nil {
+ t.Fatal("signer config")
+ }
+ log := audit.NewJSONLDecisionLog(filepath.Join(folder, "decisions.jsonl"))
+ engine.SetDecisionLog(log)
+ bindings := map[string]string{"secrets-engine": taskexecution.ServicePilotPrincipal}
+ inputs, err := buildServicePilotInputs(engine, callerauth.ModeEnforce, "flex-auth", reviewer.URL, reviewToken, ca, bindings)
+ if err != nil {
+ t.Fatal(err)
+ }
+ server := httptest.NewServer(newServeMuxWithServicePilotInputs(engine, callerauth.Disabled(), inputs))
+ defer server.Close()
+ post := func(path string, request api.CheckRequest, token string) (int, []byte) {
+ raw, _ := json.Marshal(request)
+ req, _ := http.NewRequest("POST", server.URL+path, bytes.NewReader(raw))
+ if token != "" {
+ req.Header.Set("Authorization", "Bearer "+token)
+ }
+ response, err := http.DefaultClient.Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer response.Body.Close()
+ body, _ := io.ReadAll(response.Body)
+ return response.StatusCode, body
+ }
+ original := pilotRequest(now)
+ status, body := post("/v1/check", original, pilotToken("valid", now))
+ if status != 200 {
+ t.Fatalf("status=%d", status)
+ }
+ var envelope api.DecisionEnvelope
+ if json.Unmarshal(body, &envelope) != nil {
+ t.Fatal("decode")
+ }
+ if envelope.Effect != api.DecisionEffectDeny || envelope.Reason != "service_execution_integration_unadmitted" {
+ t.Fatal("B1 changed policy gate")
+ }
+ if sign.Verify(envelope, map[string]ed25519.PublicKey{"synthetic-b1": private.Public().(ed25519.PublicKey)}) != nil {
+ t.Fatal("actual response signature")
+ }
+ if envelope.Binding.SubmittedRequestDigest != api.RequestDigest(original) || !reflect.DeepEqual(envelope.Binding.Context, original.Context) {
+ t.Fatal("original submitted request/context changed")
+ }
+ if envelope.Provenance.Caller == nil || envelope.Provenance.Caller.Mode != "enforce" || envelope.Provenance.Caller.Principal != taskexecution.ServicePilotPrincipal || envelope.Provenance.Caller.Audience != "flex-auth" {
+ t.Fatal("caller provenance")
+ }
+ records, err := log.ReadAll()
+ if err != nil || len(records) != 1 || !reflect.DeepEqual(records[0], envelope) {
+ t.Fatal("recorded signed response differs")
+ }
+ if reviews.Load() != 1 {
+ t.Fatal("request was reviewed more than once")
+ }
+ for _, kind := range []string{"foreign", "alternate-audience", "wrong-audience", "expired", "no-expiry", "unauthenticated"} {
+ t.Run(kind, func(t *testing.T) {
+ status, _ := post("/v1/check", pilotRequest(now), pilotToken(kind, now))
+ if status == 200 {
+ t.Fatal("invalid source caller accepted")
+ }
+ })
+ }
+ for _, mutate := range []func(*api.CheckRequest){func(r *api.CheckRequest) { r.Subject.ID = "other" }, func(r *api.CheckRequest) { r.Subject.Type = "human" }, func(r *api.CheckRequest) { r.Tenant = "other" }, func(r *api.CheckRequest) { r.Context["service_actor_binding"].(map[string]any)["extra"] = true }, func(r *api.CheckRequest) { r.Context["verified"] = true }, func(r *api.CheckRequest) { r.Context["delegation"] = true }, func(r *api.CheckRequest) {
+ r.Context["execution_contract"].(map[string]any)["operation_deadline"] = now.Add(5 * time.Minute).Format(time.RFC3339Nano)
+ }} {
+ request := pilotRequest(now)
+ mutate(&request)
+ if status, _ := post("/v1/check", request, pilotToken("valid", now)); status == 200 {
+ t.Fatal("invalid public request accepted")
+ }
+ }
+ if status, _ := post("/v1/check", pilotRequest(now), ""); status == 200 {
+ t.Fatal("missing caller accepted")
+ }
+ if status, _ := post("/v1/batch_check", pilotRequest(now), pilotToken("valid", now)); status != 403 {
+ t.Fatal("batch bypass")
+ }
+ records, _ = log.ReadAll()
+ if len(records) != 1 {
+ t.Fatal("refused request entered decision record")
+ }
+
+ // Exercise the actual production serve flag/configuration path in a disposable
+ // subprocess of this compiled test binary. Its environment contains only the
+ // synthetic argument handoff; no ambient backend credentials are inherited.
+ seedFile := filepath.Join(folder, "synthetic-seed")
+ os.WriteFile(seedFile, []byte(base64.RawURLEncoding.EncodeToString(make([]byte, 32))), 0440)
+ listener, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ address := listener.Addr().String()
+ listener.Close()
+ registryPath, _ := filepath.Abs(filepath.Join("..", "..", "examples", "secrets-engine", "registry_snapshot.json"))
+ policyPath, _ := filepath.Abs(filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ cliRecord := filepath.Join(folder, "cli-records.jsonl")
+ cliArgs := []string{"serve", "--addr", address, "--registry", registryPath, "--policy", policyPath, "--log", cliRecord, "--caller-auth-mode", "enforce", "--caller-audience", "flex-auth", "--caller-kubernetes-url", reviewer.URL, "--caller-reviewer-token-file", reviewToken, "--caller-ca-file", ca, "--caller-binding", "secrets-engine=" + taskexecution.ServicePilotPrincipal, "--envelope-signing-key-file", seedFile, "--envelope-signing-kid", "synthetic-b1"}
+ argsJSON, _ := json.Marshal(cliArgs)
+ child := exec.Command(os.Args[0], "-test.run=^TestServicePilotChildCLI$")
+ child.Env = []string{"ACCESS_B1_SERVER_ARGS=" + string(argsJSON)}
+ child.Stdout = io.Discard
+ child.Stderr = io.Discard
+ if child.Start() != nil {
+ t.Fatal("start disposable CLI")
+ }
+ defer func() { child.Process.Kill(); child.Wait() }()
+ ready := false
+ for attempt := 0; attempt < 100; attempt++ {
+ resp, err := http.Get("http://" + address + "/healthz")
+ if err == nil {
+ resp.Body.Close()
+ ready = true
+ break
+ }
+ time.Sleep(20 * time.Millisecond)
+ }
+ if !ready {
+ t.Fatal("disposable production serve path not ready")
+ }
+ raw, _ := json.Marshal(original)
+ cliRequest, _ := http.NewRequest("POST", "http://"+address+"/v1/check", bytes.NewReader(raw))
+ cliRequest.Header.Set("Authorization", "Bearer "+pilotToken("valid", now))
+ cliResponse, err := http.DefaultClient.Do(cliRequest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer cliResponse.Body.Close()
+ var cliEnvelope api.DecisionEnvelope
+ if cliResponse.StatusCode != 200 || json.NewDecoder(cliResponse.Body).Decode(&cliEnvelope) != nil {
+ t.Fatal("production serve path refused supported source input")
+ }
+ if cliEnvelope.Effect != api.DecisionEffectDeny || cliEnvelope.Reason != "service_execution_integration_unadmitted" || sign.Verify(cliEnvelope, map[string]ed25519.PublicKey{"synthetic-b1": private.Public().(ed25519.PublicKey)}) != nil {
+ t.Fatal("production CLI did not emit signed DENY")
+ }
+ if cliEnvelope.Binding.SubmittedRequestDigest != api.RequestDigest(original) || !reflect.DeepEqual(cliEnvelope.Binding.Context, original.Context) {
+ t.Fatal("production CLI changed original request/context")
+ }
+ cliRecords, err := audit.NewJSONLDecisionLog(cliRecord).ReadAll()
+ if err != nil || len(cliRecords) != 1 || !reflect.DeepEqual(cliRecords[0], cliEnvelope) {
+ t.Fatal("production CLI response/record differ")
+ }
+ for _, mode := range []callerauth.Mode{callerauth.ModeWarn, callerauth.ModeDisabled} {
+ if _, err := buildServicePilotInputs(engine, mode, "flex-auth", reviewer.URL, reviewToken, ca, bindings); err == nil {
+ t.Fatal("non-enforcing task mode accepted")
+ }
+ }
+ if _, err := buildServicePilotInputs(engine, callerauth.ModeEnforce, "access-engine", reviewer.URL, reviewToken, ca, bindings); err == nil {
+ t.Fatal("task pin renamed audience accepted")
+ }
+ missing := httptest.NewRecorder()
+ req := httptest.NewRequest("POST", "/v1/check", bytes.NewReader(body))
+ newServeMuxWithCallerAuth(engine, callerauth.Disabled()).ServeHTTP(missing, req)
+ if missing.Code != 503 {
+ t.Fatal("unwired generic handler accepted task pin")
+ }
+}
+func TestServicePilotGenericEngineAndCLIBypassesRefuse(t *testing.T) {
+ engine := pilotEngine(t)
+ request := pilotRequest(time.Now())
+ if _, err := engine.Check(context.Background(), request); err == nil {
+ t.Fatal("generic Check bypass")
+ }
+ if _, err := engine.BatchCheck(context.Background(), api.BatchCheckRequest{}); err == nil {
+ t.Fatal("generic Batch bypass")
+ }
+ if _, err := engine.ListAllowed(context.Background(), decision.ListAllowedRequest{}); err == nil {
+ t.Fatal("generic List bypass")
+ }
+ if _, err := engine.CheckServicePilot(context.Background(), taskexecution.ServiceInput{}); err == nil {
+ t.Fatal("zero input trusted")
+ }
+ folder := t.TempDir()
+ path := filepath.Join(folder, "request.json")
+ raw, _ := json.Marshal(request)
+ os.WriteFile(path, raw, 0600)
+ var stdout, stderr bytes.Buffer
+ code := run([]string{"check", "--registry", filepath.Join("..", "..", "examples", "secrets-engine", "registry_snapshot.json"), "--policy", filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"), "--request", path}, &stdout, &stderr)
+ if code == 0 || stdout.Len() != 0 {
+ t.Fatal("generic CLI emitted task verdict")
+ }
+}
+
+// The earlier separately named positive fixture policy is not an exception:
+// generic task actions cannot bypass the typed path on any loaded package.
+func TestServicePilotSeparatelyNamedSyntheticAllowCannotBypass(t *testing.T) {
+ p, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ p.Metadata.ID = "synthetic-legacy-stack-positive-only"
+ p.RegoModule = "package " + p.Metadata.Package + "\ndefault decision := {\"effect\":\"allow\",\"reason\":\"synthetic_only\"}\n"
+ request := pilotRequest(time.Now())
+ result, err := p.Evaluate(context.Background(), request)
+ if err != nil || result.Effect != api.DecisionEffectAllow {
+ t.Fatal("synthetic precondition did not allow")
+ }
+ engine, err := decision.NewEngine(registry.NewStore(), p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := engine.Check(context.Background(), request); err == nil {
+ t.Fatal("separately named generic task allow escaped")
+ }
+ if _, err := engine.BatchCheck(context.Background(), api.BatchCheckRequest{Action: "execute-ccr"}); err == nil {
+ t.Fatal("generic task batch escaped")
+ }
+ if _, err := engine.ListAllowed(context.Background(), decision.ListAllowedRequest{Action: "execute-ccr"}); err == nil {
+ t.Fatal("generic task list escaped")
+ }
+ raw, _ := json.Marshal(request)
+ recorder := httptest.NewRecorder()
+ req := httptest.NewRequest("POST", "/v1/check", bytes.NewReader(raw))
+ newServeMux(engine).ServeHTTP(recorder, req)
+ if recorder.Code != 503 {
+ t.Fatal("separate synthetic generic HTTP task route did not refuse")
+ }
+}
+
+func TestServicePilotWithholdsUnsignedOrAccidentalAllowBeforeRecord(t *testing.T) {
+ now := time.Now().UTC()
+ adapter, err := taskexecution.NewServicePilotInputs(fixedTokenReviewer{identity: callerauth.Identity{Username: taskexecution.ServicePilotPrincipal, Audiences: []string{"flex-auth"}, NotAfter: now.Add(time.Minute)}})
+ if err != nil {
+ t.Fatal(err)
+ }
+ input, err := adapter.Prepare(context.Background(), "Bearer synthetic", pilotRequest(now), now)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, accidentalAllow := range []bool{false, true} {
+ t.Run(map[bool]string{false: "unsigned", true: "accidental-allow"}[accidentalAllow], func(t *testing.T) {
+ p, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if accidentalAllow {
+ p.RegoModule = "package " + p.Metadata.Package + "\ndefault decision := {\"effect\":\"allow\",\"reason\":\"synthetic_only\"}\n"
+ }
+ engine, err := decision.NewEngine(registry.NewStore(), p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if accidentalAllow {
+ engine.SetEnvelopeSigningKey(ed25519.NewKeyFromSeed(make([]byte, 32)), "synthetic")
+ }
+ log := audit.NewJSONLDecisionLog(filepath.Join(t.TempDir(), "records.jsonl"))
+ engine.SetDecisionLog(log)
+ if _, err := engine.CheckServicePilot(context.Background(), input); err == nil {
+ t.Fatal("B1 released unsupported result")
+ }
+ records, err := log.ReadAll()
+ if err != nil || len(records) != 0 {
+ t.Fatal("withheld result entered recorder")
+ }
+ })
+ }
+}
+
+func TestServicePilotChildCLI(t *testing.T) {
+ raw := os.Getenv("ACCESS_B1_SERVER_ARGS")
+ if raw == "" {
+ return
+ }
+ var args []string
+ if json.Unmarshal([]byte(raw), &args) != nil {
+ os.Exit(2)
+ }
+ os.Exit(run(args, os.Stdout, os.Stderr))
+}
diff --git a/internal/decision/engine.go b/internal/decision/engine.go
--- a/internal/decision/engine.go
+++ b/internal/decision/engine.go
@@ -20,6 +20,7 @@
"github.com/netkingdom/access-engine/internal/policy"
"github.com/netkingdom/access-engine/internal/registry"
"github.com/netkingdom/access-engine/internal/sign"
+ "github.com/netkingdom/access-engine/internal/taskexecution"
"github.com/netkingdom/access-engine/pkg/api"
)
@@ -153,9 +154,44 @@
return envelope, nil
}
+// IsServicePilot selects the existing exact source package, never a request flag.
+func (e *Engine) IsServicePilot() bool {
+ return e.policy.Metadata.ID == taskexecution.ServicePilotPackageID && e.policy.Metadata.Version == taskexecution.ServicePilotPackageVersion
+}
+
+// ErrServicePilotUnavailable withholds any result that is not this B1 slice's
+// signed DENY. Provider joins and executable eligibility remain unimplemented.
+var ErrServicePilotUnavailable = errors.New("service pilot source path unavailable")
+
+// CheckServicePilot records only a signed DENY after authenticating the exact
+// immutable service input. It does not validate Approval facts or grant an act.
+func (e *Engine) CheckServicePilot(ctx context.Context, input taskexecution.ServiceInput) (api.DecisionEnvelope, error) {
+ if !e.IsServicePilot() {
+ return api.DecisionEnvelope{}, ErrServicePilotUnavailable
+ }
+ request, err := input.RequestAt(e.now())
+ if err != nil {
+ return api.DecisionEnvelope{}, err
+ }
+ envelope, err := e.decide(callerauth.WithRecord(ctx, input.Caller()), request)
+ if err != nil {
+ return api.DecisionEnvelope{}, err
+ }
+ if envelope.Effect != api.DecisionEffectDeny || envelope.Signature == nil || envelope.Signature.Mode != api.EnvelopeSignatureSigned {
+ return api.DecisionEnvelope{}, ErrServicePilotUnavailable
+ }
+ if err := e.recordDecisions([]api.DecisionEnvelope{envelope}); err != nil {
+ return api.DecisionEnvelope{}, err
+ }
+ return envelope, nil
+}
+
// Check evaluates one subject/action/resource request. The decision is
// released only after its required record commits (GH-DEC-2026-024).
func (e *Engine) Check(ctx context.Context, request api.CheckRequest) (api.DecisionEnvelope, error) {
+ if e.IsServicePilot() || request.Action == "execute-ccr" {
+ return api.DecisionEnvelope{}, ErrServicePilotUnavailable
+ }
decision, err := e.decide(ctx, request)
if err != nil {
return api.DecisionEnvelope{}, err
@@ -169,6 +205,9 @@
// BatchCheck evaluates one subject/action/context tuple against resources in
// request order, and commits the whole batch in one durable write.
func (e *Engine) BatchCheck(ctx context.Context, request api.BatchCheckRequest) ([]api.DecisionEnvelope, error) {
+ if e.IsServicePilot() || request.Action == "execute-ccr" {
+ return nil, ErrServicePilotUnavailable
+ }
decisions := make([]api.DecisionEnvelope, 0, len(request.Resources))
for _, resource := range request.Resources {
decision, err := e.decide(ctx, api.CheckRequest{
@@ -193,6 +232,9 @@
// ListAllowed evaluates candidate resources and returns only allow decisions.
func (e *Engine) ListAllowed(ctx context.Context, request ListAllowedRequest) ([]api.DecisionEnvelope, error) {
+ if e.IsServicePilot() || request.Action == "execute-ccr" {
+ return nil, ErrServicePilotUnavailable
+ }
candidates := e.store.ResourceRefs(request.System, request.ResourceType)
decisions := make([]api.DecisionEnvelope, 0, len(candidates))
for _, resource := range candidates {
diff --git a/internal/taskexecution/service_input.go b/internal/taskexecution/service_input.go
--- a/internal/taskexecution/service_input.go
+++ b/internal/taskexecution/service_input.go
@@ -1,20 +1,27 @@
package taskexecution
import (
+ "bytes"
"context"
"encoding/json"
"errors"
"github.com/netkingdom/access-engine/internal/callerauth"
"github.com/netkingdom/access-engine/pkg/api"
+ "io"
"time"
)
var ErrServiceInput = errors.New("service task execution input refused")
+const ServicePilotPackageID = "secrets-engine.task-execution.service-pilot"
+const ServicePilotPackageVersion = "v1"
+
const ServicePilotMappingVersion = "proposed-service-task-caller/v1"
const ServicePilotPrincipal = "system:serviceaccount:secrets-engine:secrets-engine"
-// ServicePilotInputs is source preparation only. It installs the explicit
+// ServicePilotInputs installs the exact B1 service input mapping for signed DENY.
+// It does not validate Approval facts or establish executable eligibility.
+// It installs the explicit
// proposed fixed subject-to-transport mapping, never a request-selected mapping.
// No constructor parameter can select warn/disabled mode or another subject.
type ServicePilotInputs struct{ auth *callerauth.Authenticator }
@@ -46,6 +53,44 @@
return r, nil
}
func (s ServiceInput) Caller() callerauth.Record { return s.caller }
+
+// RequestAt rechecks transport and operation lifetime at the runtime use site.
+// Request() remains a detached source snapshot, never an execution capability.
+func (s ServiceInput) RequestAt(now time.Time) (api.CheckRequest, error) {
+ r, err := s.Request()
+ if err != nil || now.IsZero() || s.caller.Audience != "flex-auth" || !s.caller.NotAfter.After(now) {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ contract, ok := r.Context["execution_contract"].(map[string]any)
+ if !ok {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ value, ok := contract["operation_deadline"].(string)
+ if !ok {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ deadline, err := time.Parse(time.RFC3339Nano, value)
+ if err != nil || !deadline.After(now) || deadline.After(s.caller.NotAfter) {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ return r, nil
+}
+
+// DecodeServiceRequest reuses the owner strict JSON scanner for this bounded
+// task-only route. It never changes producer canonicalization or legacy input.
+func DecodeServiceRequest(reader io.Reader) (api.CheckRequest, error) {
+ raw, err := io.ReadAll(io.LimitReader(reader, 524289))
+ var request api.CheckRequest
+ if err != nil || len(raw) > 524288 || strictObject(raw, &request) != nil {
+ return request, ErrServiceInput
+ }
+ decoder := json.NewDecoder(bytes.NewReader(raw))
+ decoder.DisallowUnknownFields()
+ if decoder.Decode(&request) != nil {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ return request, nil
+}
// DeclaredServiceBinding is public immutable metadata to bind at approval issue.
// It is not authentication evidence; Prepare independently verifies its principal.
diff --git a/internal/taskexecution/service_input_test.go b/internal/taskexecution/service_input_test.go
--- a/internal/taskexecution/service_input_test.go
+++ b/internal/taskexecution/service_input_test.go
@@ -1,7 +1,9 @@
package taskexecution
import (
+ "bytes"
"context"
+ "encoding/json"
"github.com/netkingdom/access-engine/internal/callerauth"
"github.com/netkingdom/access-engine/pkg/api"
"testing"
@@ -79,3 +81,35 @@
t.Fatal("missing owner reviewer accepted")
}
}
+
+func TestServiceInputRuntimeRecheckAndStrictHTTPDecode(t *testing.T) {
+ now := time.Now().UTC()
+ r := serviceRequest(t, now)
+ reviewer := &serviceReviewer{identity: callerauth.Identity{Username: ServicePilotPrincipal, Audiences: []string{"flex-auth"}, NotAfter: now.Add(120 * time.Second)}}
+ adapter, _ := NewServicePilotInputs(reviewer)
+ input, err := adapter.Prepare(context.Background(), "Bearer synthetic", r, now)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := input.RequestAt(now); err != nil {
+ t.Fatal(err)
+ }
+ for _, when := range []time.Time{time.Time{}, now.Add(60 * time.Second), now.Add(120 * time.Second)} {
+ if _, err := input.RequestAt(when); err == nil {
+ t.Fatal("stale/invalid runtime input accepted")
+ }
+ }
+ raw, _ := json.Marshal(r)
+ decoded, err := DecodeServiceRequest(bytes.NewReader(raw))
+ if err != nil || api.RequestDigest(decoded) != api.RequestDigest(r) {
+ t.Fatal("valid decode changed canonical submitted input")
+ }
+ bad := [][]byte{append(append([]byte{}, raw...), []byte(" {}")...), []byte(`{"action":"execute-ccr","action":"apply"}`), []byte(`{"context"
+ "bytes"
+ "encoding/json":{"human_control":true,"human_control":false}}`), []byte(`{"unknown_task_flag":true}`), bytes.Repeat([]byte(" "), 524289)}
+ for _, body := range bad {
+ if _, err := DecodeServiceRequest(bytes.NewReader(body)); err == nil {
+ t.Fatal("malformed/oversized task request accepted")
+ }
+ }
+}
diff --git a/internal/taskexecution/identity.go b/internal/taskexecution/identity.go
--- a/internal/taskexecution/identity.go
+++ b/internal/taskexecution/identity.go
@@ -1,5 +1,6 @@
-// Package taskexecution contains proposed source-only identity input validation.
-// It is not registered with HTTP evaluation and grants no executable admission.
+// Package taskexecution contains task input validation, not policy evaluation.
+// The human component remains source-only. The B1 service HTTP path can emit
+// only signed DENY and grants no executable admission.
package taskexecution
import (
diff --git a/docs/service-task-execution-contract.md b/docs/service-task-execution-contract.md
--- a/docs/service-task-execution-contract.md
+++ b/docs/service-task-execution-contract.md
@@ -51,9 +51,32 @@
Maker tests use a synthetic TokenReviewer, not actual Kubernetes authentication.
Actual deployed TokenReview origin/CA/RBAC/audience/projection rotation and mapping
-admission must be proved before runtime use. The adapter is not wired into HTTP
-routes, decision engine evaluation or a native executor; source package remains
-hard-closed. A forged public service_actor_binding alone remains insufficient.
+admission must be proved before runtime use. The B1 source path now wires the actual service adapter into `/v1/check` only
+when the exact installed service-pilot v1 package is selected. Startup requires
+enforced retained `flex-auth` audience and the one exact Secrets mapping. The
+actual reviewer authenticates once; the detached ServiceInput is rechecked for
+caller/operation expiry at use. The single existing decision engine records only
+a signed DENY on this path; unsigned output or an accidental policy allow is
+withheld before recording. No native executor or Approval fact join is installed.
+A forged public service_actor_binding alone remains insufficient.
+
+Generic Check, BatchCheck, ListAllowed and CLI/HTTP evaluation cannot evaluate
+`execute-ccr` under any package; the dedicated typed path is required. The exact
+service pin also refuses generic calls and batch requests. This deliberately
+invalidates the older separately named synthetic positive stack fixture on the
+new source. Its positive proof remains historical at Access `ec21518e`, not proof
+for B1. Do not add a fixture exception; a positive successor must use reviewed
+B2/B3 provider joins. The literal service policy false gate is unchanged.
+
+B1 retains the exact original request context, carried claim, submitted digest
+and producer-owned canonicalization. Its bounded strict JSON decoder refuses
+duplicates, trailing/unknown typed fields and oversized input. The caller's
+Approval blob remains unverified and cannot enable anything. B2 requires the
+Approval owner's fact contract/vector review; B3 additionally requires exact
+server-owned native-act representation and the complete trusted join. Fresh
+provider observations stay out of band: no refreshed context or claim replacement
+may relax strict Secrets request correspondence. Access never invokes a
+credential-carrying Python subprocess, consumes approval or performs the effect.
## Exact operation and independent gates
@@ -129,7 +152,8 @@
admit grant/baseline/estate or perform an effect. Those are explicit implementation
requirements; a matching predicate and reviewed TokenReview input are insufficient.
The service adapter carries authenticated transport evidence outside the canonical
-request but is not connected to HTTP evaluation/provider validation. A caller's
+request. B1 connects it only to signed-DENY HTTP evaluation; provider validation and
+executable eligibility remain absent. A caller's
service_actor_binding remains a public declaration independently compared by the
adapter, never a substitute for runtime provenance or exact operation authority.
diff --git a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
--- a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
+++ b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
@@ -226,3 +226,14 @@
Secrets verifier. No key, trust, image publication or live activation is admitted.
The managed workload's missing rapp declaration remains explicit migration debt;
this source binding does not fabricate a package declaration or raise its grade.
+
+
+2026-10-16 separate B1 source follow-through wires ServicePilotInputs through actual
+HTTP/TokenReview into a signed-DENY-only engine entry. Generic `execute-ccr` calls
+refuse across every policy, including the old separately named positive fixture;
+that proof stays pinned to ec21518e. Original context/submitted digest stay unchanged.
+Actual disposable serve/TLS TokenReview/HTTP signed-DENY and bypass/refusal tests are
+source evidence only. B2 depends on independently reviewed Approval fact contract
+and canonical vectors; B3 additionally needs exact source/normalization numeric
+representation and full trusted provider joins. No current policy false gate,
+production trust, native deployment or protected effect is enabled by B1.

View file

@ -0,0 +1,14 @@
diff --git a/internal/taskexecution/service_input_test.go b/internal/taskexecution/service_input_test.go
--- a/internal/taskexecution/service_input_test.go
+++ b/internal/taskexecution/service_input_test.go
@@ -104,9 +104,7 @@
if err != nil || api.RequestDigest(decoded) != api.RequestDigest(r) {
t.Fatal("valid decode changed canonical submitted input")
}
- bad := [][]byte{append(append([]byte{}, raw...), []byte(" {}")...), []byte(`{"action":"execute-ccr","action":"apply"}`), []byte(`{"context"
- "bytes"
- "encoding/json":{"human_control":true,"human_control":false}}`), []byte(`{"unknown_task_flag":true}`), bytes.Repeat([]byte(" "), 524289)}
+ bad := [][]byte{append(append([]byte{}, raw...), []byte(" {}")...), []byte(`{"action":"execute-ccr","action":"apply"}`), []byte(`{"context":{"human_control":true,"human_control":false}}`), []byte(`{"unknown_task_flag":true}`), bytes.Repeat([]byte(" "), 524289)}
for _, body := range bad {
if _, err := DecodeServiceRequest(bytes.NewReader(body)); err == nil {
t.Fatal("malformed/oversized task request accepted")

View file

@ -0,0 +1,764 @@
diff --git a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
--- a/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
+++ b/workplans/FLEX-WP-0024-decision-envelope-authenticity.md
@@ -226,3 +226,14 @@
Secrets verifier. No key, trust, image publication or live activation is admitted.
The managed workload's missing rapp declaration remains explicit migration debt;
this source binding does not fabricate a package declaration or raise its grade.
+
+
+2026-10-16 separate B1 source follow-through wires ServicePilotInputs through actual
+HTTP/TokenReview into a signed-DENY-only engine entry. Generic `execute-ccr` calls
+refuse across every policy, including the old separately named positive fixture;
+that proof stays pinned to ec21518e. Original context/submitted digest stay unchanged.
+Actual disposable serve/TLS TokenReview/HTTP signed-DENY and bypass/refusal tests are
+source evidence only. B2 depends on independently reviewed Approval fact contract
+and canonical vectors; B3 additionally needs exact source/normalization numeric
+representation and full trusted provider joins. No current policy false gate,
+production trust, native deployment or protected effect is enabled by B1.
diff --git a/cmd/access-engine/main.go b/cmd/access-engine/main.go
--- a/cmd/access-engine/main.go
+++ b/cmd/access-engine/main.go
@@ -22,6 +22,7 @@
"github.com/netkingdom/access-engine/internal/emission"
"github.com/netkingdom/access-engine/internal/policy"
"github.com/netkingdom/access-engine/internal/registry"
+ "github.com/netkingdom/access-engine/internal/taskexecution"
"github.com/netkingdom/access-engine/pkg/api"
)
@@ -397,7 +398,11 @@
return fail(stderr, err)
}
- mux := newServeMuxWithCallerAuth(engine, authenticator)
+ serviceInputs, err := buildServicePilotInputs(engine, callerauth.Mode(*callerAuthMode), *callerAudience, *callerKubernetesURL, *callerReviewerTokenFile, *callerCAFile, callerBindings.StringMap())
+ if err != nil {
+ return fail(stderr, err)
+ }
+ mux := newServeMuxWithServicePilotInputs(engine, authenticator, serviceInputs)
if outbox != nil {
mountEmission(mux, outbox)
}
@@ -448,6 +453,10 @@
}
func newServeMuxWithCallerAuth(engine *decisioncore.Engine, authenticator *callerauth.Authenticator) *http.ServeMux {
+ return newServeMuxWithServicePilotInputs(engine, authenticator, nil)
+}
+
+func newServeMuxWithServicePilotInputs(engine *decisioncore.Engine, authenticator *callerauth.Authenticator, serviceInputs *taskexecution.ServicePilotInputs) *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("content-type", "application/json")
@@ -458,6 +467,25 @@
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
+ if engine.IsServicePilot() {
+ if serviceInputs == nil {
+ http.Error(w, "service input unavailable", http.StatusServiceUnavailable)
+ return
+ }
+ request, err := taskexecution.DecodeServiceRequest(r.Body)
+ if err != nil || request.Purpose != "" {
+ http.Error(w, "invalid service task request", http.StatusBadRequest)
+ return
+ }
+ input, err := serviceInputs.Prepare(r.Context(), r.Header.Get("Authorization"), request, time.Now().UTC())
+ if err != nil {
+ http.Error(w, "service task input refused", http.StatusForbidden)
+ return
+ }
+ decision, err := engine.CheckServicePilot(r.Context(), input)
+ writeHTTP(w, decision, err)
+ return
+ }
var request api.CheckRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -478,6 +506,10 @@
writeHTTP(w, decision, err)
})
mux.HandleFunc("/v1/batch_check", func(w http.ResponseWriter, r *http.Request) {
+ if engine.IsServicePilot() {
+ http.Error(w, "service task batch unavailable", http.StatusForbidden)
+ return
+ }
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
@@ -500,6 +532,22 @@
writeHTTP(w, decisions, err)
})
return mux
+}
+
+// Only the existing exact service pilot package selects this source path.
+// This is not an eligibility toggle; successful preparation still only DENYs.
+func buildServicePilotInputs(engine *decisioncore.Engine, mode callerauth.Mode, audience, kubernetesURL, reviewerTokenFile, caFile string, bindings map[string]string) (*taskexecution.ServicePilotInputs, error) {
+ if !engine.IsServicePilot() {
+ return nil, nil
+ }
+ if mode != callerauth.ModeEnforce || audience != "flex-auth" || len(bindings) != 1 || bindings["secrets-engine"] != taskexecution.ServicePilotPrincipal {
+ return nil, fmt.Errorf("service pilot requires exact enforced retained caller mapping")
+ }
+ reviewer, err := callerauth.NewKubernetesTokenReviewer(kubernetesURL, audience, reviewerTokenFile, caFile)
+ if err != nil {
+ return nil, err
+ }
+ return taskexecution.NewServicePilotInputs(reviewer)
}
func buildCallerAuthenticator(mode callerauth.Mode, audience, kubernetesURL, reviewerTokenFile, caFile string, bindings map[string]string, stderr io.Writer) (*callerauth.Authenticator, error) {
@@ -615,7 +663,7 @@
func writeHTTP(w http.ResponseWriter, value any, err error) {
w.Header().Set("content-type", "application/json")
- if errors.Is(err, decisioncore.ErrRecordNotCommitted) {
+ if errors.Is(err, decisioncore.ErrRecordNotCommitted) || errors.Is(err, decisioncore.ErrServicePilotUnavailable) {
// Unavailable evaluation is not an authorization verdict (GH-DEC-2026-024).
http.Error(w, err.Error(), http.StatusServiceUnavailable)
return
diff --git a/cmd/access-engine/service_pilot_http_test.go b/cmd/access-engine/service_pilot_http_test.go
new file mode 100644
--- /dev/null
+++ b/cmd/access-engine/service_pilot_http_test.go
@@ -0,0 +1,376 @@
+package main
+
+import (
+ "bytes"
+ "context"
+ "crypto/ed25519"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "io"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "github.com/netkingdom/access-engine/internal/audit"
+ "github.com/netkingdom/access-engine/internal/callerauth"
+ "github.com/netkingdom/access-engine/internal/decision"
+ "github.com/netkingdom/access-engine/internal/policy"
+ "github.com/netkingdom/access-engine/internal/registry"
+ "github.com/netkingdom/access-engine/internal/sign"
+ "github.com/netkingdom/access-engine/internal/taskexecution"
+ "github.com/netkingdom/access-engine/pkg/api"
+)
+
+func pilotRequest(now time.Time) api.CheckRequest {
+ return api.CheckRequest{ID: "source-only-service-http", Tenant: "tenant:platform", Subject: api.SubjectRef{ID: "secrets-engine", Type: "service"}, Action: "execute-ccr", Resource: api.ResourceRef{ID: "catalog:synthetic", Type: "secret-catalog-lane", System: "secrets-engine", Attributes: map[string]any{"stage": "prod"}}, Context: map[string]any{"human_control": true, "service_actor_binding": taskexecution.DeclaredServiceBinding(), "execution_contract": map[string]any{"contract_version": "SECRETS-EXEC-20261011-v1", "operation": "metadata-apply-ccr", "execution_actor": "service:secrets-engine", "operation_deadline": now.Add(30 * time.Second).Format(time.RFC3339Nano)}, "approval": map[string]any{"caller_claim": "unchanged and untrusted in B1"}}}
+}
+func pilotEngine(t *testing.T) *decision.Engine {
+ t.Helper()
+ p, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ engine, err := decision.NewEngine(registry.NewStore(), p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return engine
+}
+func pilotToken(kind string, now time.Time) string {
+ payload := map[string]any{"case": kind, "exp": now.Add(2 * time.Minute).Unix()}
+ if kind == "expired" {
+ payload["exp"] = now.Add(-time.Second).Unix()
+ }
+ if kind == "no-expiry" {
+ delete(payload, "exp")
+ }
+ raw, _ := json.Marshal(payload)
+ return "synthetic." + base64.RawURLEncoding.EncodeToString(raw) + ".not-a-real-token"
+}
+func TestServicePilotRealHTTPAndTokenReviewSignedDeny(t *testing.T) {
+ now := time.Now().UTC()
+ var reviews atomic.Int64
+ reviewer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ reviews.Add(1)
+ if r.URL.Path != "/apis/authentication.k8s.io/v1/tokenreviews" || r.Header.Get("Authorization") != "Bearer synthetic-reviewer" {
+ t.Error("unexpected reviewer route/auth")
+ w.WriteHeader(403)
+ return
+ }
+ var body struct {
+ Spec struct {
+ Token string `json:"token"`
+ Audiences []string `json:"audiences"`
+ } `json:"spec"`
+ }
+ if json.NewDecoder(r.Body).Decode(&body) != nil {
+ w.WriteHeader(400)
+ return
+ }
+ if !reflect.DeepEqual(body.Spec.Audiences, []string{"flex-auth", "access-engine"}) {
+ t.Error("unexpected bounded runtime audience request")
+ }
+ pieces := strings.Split(body.Spec.Token, ".")
+ raw, _ := base64.RawURLEncoding.DecodeString(pieces[1])
+ var claims map[string]any
+ json.Unmarshal(raw, &claims)
+ principal := taskexecution.ServicePilotPrincipal
+ audiences := []string{"flex-auth"}
+ authenticated := true
+ switch claims["case"] {
+ case "foreign":
+ principal = "system:serviceaccount:other:other"
+ case "alternate-audience":
+ audiences = []string{"access-engine"}
+ case "wrong-audience":
+ audiences = []string{"openbao"}
+ case "unauthenticated":
+ authenticated = false
+ }
+ json.NewEncoder(w).Encode(map[string]any{"status": map[string]any{"authenticated": authenticated, "audiences": audiences, "user": map[string]any{"username": principal}}})
+ }))
+ defer reviewer.Close()
+ folder := t.TempDir()
+ ca := filepath.Join(folder, "ca.crt")
+ reviewToken := filepath.Join(folder, "reviewer-token")
+ os.WriteFile(ca, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: reviewer.Certificate().Raw}), 0600)
+ os.WriteFile(reviewToken, []byte("synthetic-reviewer"), 0600)
+ engine := pilotEngine(t)
+ private := ed25519.NewKeyFromSeed(make([]byte, 32))
+ if engine.SetEnvelopeSigningKey(private, "synthetic-b1") != nil {
+ t.Fatal("signer config")
+ }
+ log := audit.NewJSONLDecisionLog(filepath.Join(folder, "decisions.jsonl"))
+ engine.SetDecisionLog(log)
+ bindings := map[string]string{"secrets-engine": taskexecution.ServicePilotPrincipal}
+ inputs, err := buildServicePilotInputs(engine, callerauth.ModeEnforce, "flex-auth", reviewer.URL, reviewToken, ca, bindings)
+ if err != nil {
+ t.Fatal(err)
+ }
+ server := httptest.NewServer(newServeMuxWithServicePilotInputs(engine, callerauth.Disabled(), inputs))
+ defer server.Close()
+ post := func(path string, request api.CheckRequest, token string) (int, []byte) {
+ raw, _ := json.Marshal(request)
+ req, _ := http.NewRequest("POST", server.URL+path, bytes.NewReader(raw))
+ if token != "" {
+ req.Header.Set("Authorization", "Bearer "+token)
+ }
+ response, err := http.DefaultClient.Do(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer response.Body.Close()
+ body, _ := io.ReadAll(response.Body)
+ return response.StatusCode, body
+ }
+ original := pilotRequest(now)
+ status, body := post("/v1/check", original, pilotToken("valid", now))
+ if status != 200 {
+ t.Fatalf("status=%d", status)
+ }
+ var envelope api.DecisionEnvelope
+ if json.Unmarshal(body, &envelope) != nil {
+ t.Fatal("decode")
+ }
+ if envelope.Effect != api.DecisionEffectDeny || envelope.Reason != "service_execution_integration_unadmitted" {
+ t.Fatal("B1 changed policy gate")
+ }
+ if sign.Verify(envelope, map[string]ed25519.PublicKey{"synthetic-b1": private.Public().(ed25519.PublicKey)}) != nil {
+ t.Fatal("actual response signature")
+ }
+ if envelope.Binding.SubmittedRequestDigest != api.RequestDigest(original) || !reflect.DeepEqual(envelope.Binding.Context, original.Context) {
+ t.Fatal("original submitted request/context changed")
+ }
+ if envelope.Provenance.Caller == nil || envelope.Provenance.Caller.Mode != "enforce" || envelope.Provenance.Caller.Principal != taskexecution.ServicePilotPrincipal || envelope.Provenance.Caller.Audience != "flex-auth" {
+ t.Fatal("caller provenance")
+ }
+ records, err := log.ReadAll()
+ if err != nil || len(records) != 1 || !reflect.DeepEqual(records[0], envelope) {
+ t.Fatal("recorded signed response differs")
+ }
+ if reviews.Load() != 1 {
+ t.Fatal("request was reviewed more than once")
+ }
+ for _, kind := range []string{"foreign", "alternate-audience", "wrong-audience", "expired", "no-expiry", "unauthenticated"} {
+ t.Run(kind, func(t *testing.T) {
+ status, _ := post("/v1/check", pilotRequest(now), pilotToken(kind, now))
+ if status == 200 {
+ t.Fatal("invalid source caller accepted")
+ }
+ })
+ }
+ for _, mutate := range []func(*api.CheckRequest){func(r *api.CheckRequest) { r.Subject.ID = "other" }, func(r *api.CheckRequest) { r.Subject.Type = "human" }, func(r *api.CheckRequest) { r.Tenant = "other" }, func(r *api.CheckRequest) { r.Context["service_actor_binding"].(map[string]any)["extra"] = true }, func(r *api.CheckRequest) { r.Context["verified"] = true }, func(r *api.CheckRequest) { r.Context["delegation"] = true }, func(r *api.CheckRequest) {
+ r.Context["execution_contract"].(map[string]any)["operation_deadline"] = now.Add(5 * time.Minute).Format(time.RFC3339Nano)
+ }} {
+ request := pilotRequest(now)
+ mutate(&request)
+ if status, _ := post("/v1/check", request, pilotToken("valid", now)); status == 200 {
+ t.Fatal("invalid public request accepted")
+ }
+ }
+ if status, _ := post("/v1/check", pilotRequest(now), ""); status == 200 {
+ t.Fatal("missing caller accepted")
+ }
+ if status, _ := post("/v1/batch_check", pilotRequest(now), pilotToken("valid", now)); status != 403 {
+ t.Fatal("batch bypass")
+ }
+ records, _ = log.ReadAll()
+ if len(records) != 1 {
+ t.Fatal("refused request entered decision record")
+ }
+
+ // Exercise the actual production serve flag/configuration path in a disposable
+ // subprocess of this compiled test binary. Its environment contains only the
+ // synthetic argument handoff; no ambient backend credentials are inherited.
+ seedFile := filepath.Join(folder, "synthetic-seed")
+ os.WriteFile(seedFile, []byte(base64.RawURLEncoding.EncodeToString(make([]byte, 32))), 0440)
+ listener, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatal(err)
+ }
+ address := listener.Addr().String()
+ listener.Close()
+ registryPath, _ := filepath.Abs(filepath.Join("..", "..", "examples", "secrets-engine", "registry_snapshot.json"))
+ policyPath, _ := filepath.Abs(filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ cliRecord := filepath.Join(folder, "cli-records.jsonl")
+ cliArgs := []string{"serve", "--addr", address, "--registry", registryPath, "--policy", policyPath, "--log", cliRecord, "--caller-auth-mode", "enforce", "--caller-audience", "flex-auth", "--caller-kubernetes-url", reviewer.URL, "--caller-reviewer-token-file", reviewToken, "--caller-ca-file", ca, "--caller-binding", "secrets-engine=" + taskexecution.ServicePilotPrincipal, "--envelope-signing-key-file", seedFile, "--envelope-signing-kid", "synthetic-b1"}
+ argsJSON, _ := json.Marshal(cliArgs)
+ child := exec.Command(os.Args[0], "-test.run=^TestServicePilotChildCLI$")
+ child.Env = []string{"ACCESS_B1_SERVER_ARGS=" + string(argsJSON)}
+ child.Stdout = io.Discard
+ child.Stderr = io.Discard
+ if child.Start() != nil {
+ t.Fatal("start disposable CLI")
+ }
+ defer func() { child.Process.Kill(); child.Wait() }()
+ ready := false
+ for attempt := 0; attempt < 100; attempt++ {
+ resp, err := http.Get("http://" + address + "/healthz")
+ if err == nil {
+ resp.Body.Close()
+ ready = true
+ break
+ }
+ time.Sleep(20 * time.Millisecond)
+ }
+ if !ready {
+ t.Fatal("disposable production serve path not ready")
+ }
+ raw, _ := json.Marshal(original)
+ cliRequest, _ := http.NewRequest("POST", "http://"+address+"/v1/check", bytes.NewReader(raw))
+ cliRequest.Header.Set("Authorization", "Bearer "+pilotToken("valid", now))
+ cliResponse, err := http.DefaultClient.Do(cliRequest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer cliResponse.Body.Close()
+ var cliEnvelope api.DecisionEnvelope
+ if cliResponse.StatusCode != 200 || json.NewDecoder(cliResponse.Body).Decode(&cliEnvelope) != nil {
+ t.Fatal("production serve path refused supported source input")
+ }
+ if cliEnvelope.Effect != api.DecisionEffectDeny || cliEnvelope.Reason != "service_execution_integration_unadmitted" || sign.Verify(cliEnvelope, map[string]ed25519.PublicKey{"synthetic-b1": private.Public().(ed25519.PublicKey)}) != nil {
+ t.Fatal("production CLI did not emit signed DENY")
+ }
+ if cliEnvelope.Binding.SubmittedRequestDigest != api.RequestDigest(original) || !reflect.DeepEqual(cliEnvelope.Binding.Context, original.Context) {
+ t.Fatal("production CLI changed original request/context")
+ }
+ cliRecords, err := audit.NewJSONLDecisionLog(cliRecord).ReadAll()
+ if err != nil || len(cliRecords) != 1 || !reflect.DeepEqual(cliRecords[0], cliEnvelope) {
+ t.Fatal("production CLI response/record differ")
+ }
+ for _, mode := range []callerauth.Mode{callerauth.ModeWarn, callerauth.ModeDisabled} {
+ if _, err := buildServicePilotInputs(engine, mode, "flex-auth", reviewer.URL, reviewToken, ca, bindings); err == nil {
+ t.Fatal("non-enforcing task mode accepted")
+ }
+ }
+ if _, err := buildServicePilotInputs(engine, callerauth.ModeEnforce, "access-engine", reviewer.URL, reviewToken, ca, bindings); err == nil {
+ t.Fatal("task pin renamed audience accepted")
+ }
+ missing := httptest.NewRecorder()
+ req := httptest.NewRequest("POST", "/v1/check", bytes.NewReader(body))
+ newServeMuxWithCallerAuth(engine, callerauth.Disabled()).ServeHTTP(missing, req)
+ if missing.Code != 503 {
+ t.Fatal("unwired generic handler accepted task pin")
+ }
+}
+func TestServicePilotGenericEngineAndCLIBypassesRefuse(t *testing.T) {
+ engine := pilotEngine(t)
+ request := pilotRequest(time.Now())
+ if _, err := engine.Check(context.Background(), request); err == nil {
+ t.Fatal("generic Check bypass")
+ }
+ if _, err := engine.BatchCheck(context.Background(), api.BatchCheckRequest{}); err == nil {
+ t.Fatal("generic Batch bypass")
+ }
+ if _, err := engine.ListAllowed(context.Background(), decision.ListAllowedRequest{}); err == nil {
+ t.Fatal("generic List bypass")
+ }
+ if _, err := engine.CheckServicePilot(context.Background(), taskexecution.ServiceInput{}); err == nil {
+ t.Fatal("zero input trusted")
+ }
+ folder := t.TempDir()
+ path := filepath.Join(folder, "request.json")
+ raw, _ := json.Marshal(request)
+ os.WriteFile(path, raw, 0600)
+ var stdout, stderr bytes.Buffer
+ code := run([]string{"check", "--registry", filepath.Join("..", "..", "examples", "secrets-engine", "registry_snapshot.json"), "--policy", filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"), "--request", path}, &stdout, &stderr)
+ if code == 0 || stdout.Len() != 0 {
+ t.Fatal("generic CLI emitted task verdict")
+ }
+}
+
+// The earlier separately named positive fixture policy is not an exception:
+// generic task actions cannot bypass the typed path on any loaded package.
+func TestServicePilotSeparatelyNamedSyntheticAllowCannotBypass(t *testing.T) {
+ p, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ p.Metadata.ID = "synthetic-legacy-stack-positive-only"
+ p.RegoModule = "package " + p.Metadata.Package + "\ndefault decision := {\"effect\":\"allow\",\"reason\":\"synthetic_only\"}\n"
+ request := pilotRequest(time.Now())
+ result, err := p.Evaluate(context.Background(), request)
+ if err != nil || result.Effect != api.DecisionEffectAllow {
+ t.Fatal("synthetic precondition did not allow")
+ }
+ engine, err := decision.NewEngine(registry.NewStore(), p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := engine.Check(context.Background(), request); err == nil {
+ t.Fatal("separately named generic task allow escaped")
+ }
+ if _, err := engine.BatchCheck(context.Background(), api.BatchCheckRequest{Action: "execute-ccr"}); err == nil {
+ t.Fatal("generic task batch escaped")
+ }
+ if _, err := engine.ListAllowed(context.Background(), decision.ListAllowedRequest{Action: "execute-ccr"}); err == nil {
+ t.Fatal("generic task list escaped")
+ }
+ raw, _ := json.Marshal(request)
+ recorder := httptest.NewRecorder()
+ req := httptest.NewRequest("POST", "/v1/check", bytes.NewReader(raw))
+ newServeMux(engine).ServeHTTP(recorder, req)
+ if recorder.Code != 503 {
+ t.Fatal("separate synthetic generic HTTP task route did not refuse")
+ }
+}
+
+func TestServicePilotWithholdsUnsignedOrAccidentalAllowBeforeRecord(t *testing.T) {
+ now := time.Now().UTC()
+ adapter, err := taskexecution.NewServicePilotInputs(fixedTokenReviewer{identity: callerauth.Identity{Username: taskexecution.ServicePilotPrincipal, Audiences: []string{"flex-auth"}, NotAfter: now.Add(time.Minute)}})
+ if err != nil {
+ t.Fatal(err)
+ }
+ input, err := adapter.Prepare(context.Background(), "Bearer synthetic", pilotRequest(now), now)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, accidentalAllow := range []bool{false, true} {
+ t.Run(map[bool]string{false: "unsigned", true: "accidental-allow"}[accidentalAllow], func(t *testing.T) {
+ p, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "secrets-engine-service-task-execution", "policy_package.md"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if accidentalAllow {
+ p.RegoModule = "package " + p.Metadata.Package + "\ndefault decision := {\"effect\":\"allow\",\"reason\":\"synthetic_only\"}\n"
+ }
+ engine, err := decision.NewEngine(registry.NewStore(), p)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if accidentalAllow {
+ engine.SetEnvelopeSigningKey(ed25519.NewKeyFromSeed(make([]byte, 32)), "synthetic")
+ }
+ log := audit.NewJSONLDecisionLog(filepath.Join(t.TempDir(), "records.jsonl"))
+ engine.SetDecisionLog(log)
+ if _, err := engine.CheckServicePilot(context.Background(), input); err == nil {
+ t.Fatal("B1 released unsupported result")
+ }
+ records, err := log.ReadAll()
+ if err != nil || len(records) != 0 {
+ t.Fatal("withheld result entered recorder")
+ }
+ })
+ }
+}
+
+func TestServicePilotChildCLI(t *testing.T) {
+ raw := os.Getenv("ACCESS_B1_SERVER_ARGS")
+ if raw == "" {
+ return
+ }
+ var args []string
+ if json.Unmarshal([]byte(raw), &args) != nil {
+ os.Exit(2)
+ }
+ os.Exit(run(args, os.Stdout, os.Stderr))
+}
diff --git a/internal/decision/engine.go b/internal/decision/engine.go
--- a/internal/decision/engine.go
+++ b/internal/decision/engine.go
@@ -20,6 +20,7 @@
"github.com/netkingdom/access-engine/internal/policy"
"github.com/netkingdom/access-engine/internal/registry"
"github.com/netkingdom/access-engine/internal/sign"
+ "github.com/netkingdom/access-engine/internal/taskexecution"
"github.com/netkingdom/access-engine/pkg/api"
)
@@ -153,9 +154,44 @@
return envelope, nil
}
+// IsServicePilot selects the existing exact source package, never a request flag.
+func (e *Engine) IsServicePilot() bool {
+ return e.policy.Metadata.ID == taskexecution.ServicePilotPackageID && e.policy.Metadata.Version == taskexecution.ServicePilotPackageVersion
+}
+
+// ErrServicePilotUnavailable withholds any result that is not this B1 slice's
+// signed DENY. Provider joins and executable eligibility remain unimplemented.
+var ErrServicePilotUnavailable = errors.New("service pilot source path unavailable")
+
+// CheckServicePilot records only a signed DENY after authenticating the exact
+// immutable service input. It does not validate Approval facts or grant an act.
+func (e *Engine) CheckServicePilot(ctx context.Context, input taskexecution.ServiceInput) (api.DecisionEnvelope, error) {
+ if !e.IsServicePilot() {
+ return api.DecisionEnvelope{}, ErrServicePilotUnavailable
+ }
+ request, err := input.RequestAt(e.now())
+ if err != nil {
+ return api.DecisionEnvelope{}, err
+ }
+ envelope, err := e.decide(callerauth.WithRecord(ctx, input.Caller()), request)
+ if err != nil {
+ return api.DecisionEnvelope{}, err
+ }
+ if envelope.Effect != api.DecisionEffectDeny || envelope.Signature == nil || envelope.Signature.Mode != api.EnvelopeSignatureSigned {
+ return api.DecisionEnvelope{}, ErrServicePilotUnavailable
+ }
+ if err := e.recordDecisions([]api.DecisionEnvelope{envelope}); err != nil {
+ return api.DecisionEnvelope{}, err
+ }
+ return envelope, nil
+}
+
// Check evaluates one subject/action/resource request. The decision is
// released only after its required record commits (GH-DEC-2026-024).
func (e *Engine) Check(ctx context.Context, request api.CheckRequest) (api.DecisionEnvelope, error) {
+ if e.IsServicePilot() || request.Action == "execute-ccr" {
+ return api.DecisionEnvelope{}, ErrServicePilotUnavailable
+ }
decision, err := e.decide(ctx, request)
if err != nil {
return api.DecisionEnvelope{}, err
@@ -169,6 +205,9 @@
// BatchCheck evaluates one subject/action/context tuple against resources in
// request order, and commits the whole batch in one durable write.
func (e *Engine) BatchCheck(ctx context.Context, request api.BatchCheckRequest) ([]api.DecisionEnvelope, error) {
+ if e.IsServicePilot() || request.Action == "execute-ccr" {
+ return nil, ErrServicePilotUnavailable
+ }
decisions := make([]api.DecisionEnvelope, 0, len(request.Resources))
for _, resource := range request.Resources {
decision, err := e.decide(ctx, api.CheckRequest{
@@ -193,6 +232,9 @@
// ListAllowed evaluates candidate resources and returns only allow decisions.
func (e *Engine) ListAllowed(ctx context.Context, request ListAllowedRequest) ([]api.DecisionEnvelope, error) {
+ if e.IsServicePilot() || request.Action == "execute-ccr" {
+ return nil, ErrServicePilotUnavailable
+ }
candidates := e.store.ResourceRefs(request.System, request.ResourceType)
decisions := make([]api.DecisionEnvelope, 0, len(candidates))
for _, resource := range candidates {
diff --git a/internal/taskexecution/service_input.go b/internal/taskexecution/service_input.go
--- a/internal/taskexecution/service_input.go
+++ b/internal/taskexecution/service_input.go
@@ -1,20 +1,27 @@
package taskexecution
import (
+ "bytes"
"context"
"encoding/json"
"errors"
"github.com/netkingdom/access-engine/internal/callerauth"
"github.com/netkingdom/access-engine/pkg/api"
+ "io"
"time"
)
var ErrServiceInput = errors.New("service task execution input refused")
+const ServicePilotPackageID = "secrets-engine.task-execution.service-pilot"
+const ServicePilotPackageVersion = "v1"
+
const ServicePilotMappingVersion = "proposed-service-task-caller/v1"
const ServicePilotPrincipal = "system:serviceaccount:secrets-engine:secrets-engine"
-// ServicePilotInputs is source preparation only. It installs the explicit
+// ServicePilotInputs installs the exact B1 service input mapping for signed DENY.
+// It does not validate Approval facts or establish executable eligibility.
+// It installs the explicit
// proposed fixed subject-to-transport mapping, never a request-selected mapping.
// No constructor parameter can select warn/disabled mode or another subject.
type ServicePilotInputs struct{ auth *callerauth.Authenticator }
@@ -46,6 +53,44 @@
return r, nil
}
func (s ServiceInput) Caller() callerauth.Record { return s.caller }
+
+// RequestAt rechecks transport and operation lifetime at the runtime use site.
+// Request() remains a detached source snapshot, never an execution capability.
+func (s ServiceInput) RequestAt(now time.Time) (api.CheckRequest, error) {
+ r, err := s.Request()
+ if err != nil || now.IsZero() || s.caller.Audience != "flex-auth" || !s.caller.NotAfter.After(now) {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ contract, ok := r.Context["execution_contract"].(map[string]any)
+ if !ok {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ value, ok := contract["operation_deadline"].(string)
+ if !ok {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ deadline, err := time.Parse(time.RFC3339Nano, value)
+ if err != nil || !deadline.After(now) || deadline.After(s.caller.NotAfter) {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ return r, nil
+}
+
+// DecodeServiceRequest reuses the owner strict JSON scanner for this bounded
+// task-only route. It never changes producer canonicalization or legacy input.
+func DecodeServiceRequest(reader io.Reader) (api.CheckRequest, error) {
+ raw, err := io.ReadAll(io.LimitReader(reader, 524289))
+ var request api.CheckRequest
+ if err != nil || len(raw) > 524288 || strictObject(raw, &request) != nil {
+ return request, ErrServiceInput
+ }
+ decoder := json.NewDecoder(bytes.NewReader(raw))
+ decoder.DisallowUnknownFields()
+ if decoder.Decode(&request) != nil {
+ return api.CheckRequest{}, ErrServiceInput
+ }
+ return request, nil
+}
// DeclaredServiceBinding is public immutable metadata to bind at approval issue.
// It is not authentication evidence; Prepare independently verifies its principal.
diff --git a/internal/taskexecution/service_input_test.go b/internal/taskexecution/service_input_test.go
--- a/internal/taskexecution/service_input_test.go
+++ b/internal/taskexecution/service_input_test.go
@@ -1,7 +1,9 @@
package taskexecution
import (
+ "bytes"
"context"
+ "encoding/json"
"github.com/netkingdom/access-engine/internal/callerauth"
"github.com/netkingdom/access-engine/pkg/api"
"testing"
@@ -79,3 +81,33 @@
t.Fatal("missing owner reviewer accepted")
}
}
+
+func TestServiceInputRuntimeRecheckAndStrictHTTPDecode(t *testing.T) {
+ now := time.Now().UTC()
+ r := serviceRequest(t, now)
+ reviewer := &serviceReviewer{identity: callerauth.Identity{Username: ServicePilotPrincipal, Audiences: []string{"flex-auth"}, NotAfter: now.Add(120 * time.Second)}}
+ adapter, _ := NewServicePilotInputs(reviewer)
+ input, err := adapter.Prepare(context.Background(), "Bearer synthetic", r, now)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := input.RequestAt(now); err != nil {
+ t.Fatal(err)
+ }
+ for _, when := range []time.Time{time.Time{}, now.Add(60 * time.Second), now.Add(120 * time.Second)} {
+ if _, err := input.RequestAt(when); err == nil {
+ t.Fatal("stale/invalid runtime input accepted")
+ }
+ }
+ raw, _ := json.Marshal(r)
+ decoded, err := DecodeServiceRequest(bytes.NewReader(raw))
+ if err != nil || api.RequestDigest(decoded) != api.RequestDigest(r) {
+ t.Fatal("valid decode changed canonical submitted input")
+ }
+ bad := [][]byte{append(append([]byte{}, raw...), []byte(" {}")...), []byte(`{"action":"execute-ccr","action":"apply"}`), []byte(`{"context":{"human_control":true,"human_control":false}}`), []byte(`{"unknown_task_flag":true}`), bytes.Repeat([]byte(" "), 524289)}
+ for _, body := range bad {
+ if _, err := DecodeServiceRequest(bytes.NewReader(body)); err == nil {
+ t.Fatal("malformed/oversized task request accepted")
+ }
+ }
+}
diff --git a/internal/taskexecution/identity.go b/internal/taskexecution/identity.go
--- a/internal/taskexecution/identity.go
+++ b/internal/taskexecution/identity.go
@@ -1,5 +1,6 @@
-// Package taskexecution contains proposed source-only identity input validation.
-// It is not registered with HTTP evaluation and grants no executable admission.
+// Package taskexecution contains task input validation, not policy evaluation.
+// The human component remains source-only. The B1 service HTTP path can emit
+// only signed DENY and grants no executable admission.
package taskexecution
import (
diff --git a/docs/service-task-execution-contract.md b/docs/service-task-execution-contract.md
--- a/docs/service-task-execution-contract.md
+++ b/docs/service-task-execution-contract.md
@@ -51,9 +51,32 @@
Maker tests use a synthetic TokenReviewer, not actual Kubernetes authentication.
Actual deployed TokenReview origin/CA/RBAC/audience/projection rotation and mapping
-admission must be proved before runtime use. The adapter is not wired into HTTP
-routes, decision engine evaluation or a native executor; source package remains
-hard-closed. A forged public service_actor_binding alone remains insufficient.
+admission must be proved before runtime use. The B1 source path now wires the actual service adapter into `/v1/check` only
+when the exact installed service-pilot v1 package is selected. Startup requires
+enforced retained `flex-auth` audience and the one exact Secrets mapping. The
+actual reviewer authenticates once; the detached ServiceInput is rechecked for
+caller/operation expiry at use. The single existing decision engine records only
+a signed DENY on this path; unsigned output or an accidental policy allow is
+withheld before recording. No native executor or Approval fact join is installed.
+A forged public service_actor_binding alone remains insufficient.
+
+Generic Check, BatchCheck, ListAllowed and CLI/HTTP evaluation cannot evaluate
+`execute-ccr` under any package; the dedicated typed path is required. The exact
+service pin also refuses generic calls and batch requests. This deliberately
+invalidates the older separately named synthetic positive stack fixture on the
+new source. Its positive proof remains historical at Access `ec21518e`, not proof
+for B1. Do not add a fixture exception; a positive successor must use reviewed
+B2/B3 provider joins. The literal service policy false gate is unchanged.
+
+B1 retains the exact original request context, carried claim, submitted digest
+and producer-owned canonicalization. Its bounded strict JSON decoder refuses
+duplicates, trailing/unknown typed fields and oversized input. The caller's
+Approval blob remains unverified and cannot enable anything. B2 requires the
+Approval owner's fact contract/vector review; B3 additionally requires exact
+server-owned native-act representation and the complete trusted join. Fresh
+provider observations stay out of band: no refreshed context or claim replacement
+may relax strict Secrets request correspondence. Access never invokes a
+credential-carrying Python subprocess, consumes approval or performs the effect.
## Exact operation and independent gates
@@ -129,7 +152,8 @@
admit grant/baseline/estate or perform an effect. Those are explicit implementation
requirements; a matching predicate and reviewed TokenReview input are insufficient.
The service adapter carries authenticated transport evidence outside the canonical
-request but is not connected to HTTP evaluation/provider validation. A caller's
+request. B1 connects it only to signed-DENY HTTP evaluation; provider validation and
+executable eligibility remain absent. A caller's
service_actor_binding remains a public declaration independently compared by the
adapter, never a substitute for runtime provenance or exact operation authority.