build: derive coordination receiver from staged owner sources

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a15374-f59f-7a31-86d2-d23b5d2ed181
This commit is contained in:
tegwick 2026-10-19 13:44:36 +02:00
parent 4c7f32adc7
commit bde143e030
9 changed files with 284 additions and 22 deletions

View file

@ -3,6 +3,8 @@ CUSTODIAN_SOURCE ?= /home/worsch/the-custodian
GENERATED_REPO_SLUG ?= net-kingdom
.PHONY: generated-work-records generated-work-records-check
generated-work-records:
python3 tools/coordination-frame/staged.py --repo .
python3 "$(CUSTODIAN_SOURCE)/scripts/generate_staged_work_records.py" --repo . --repo-slug "$(GENERATED_REPO_SLUG)"
generated-work-records-check:
python3 tools/coordination-frame/staged.py --repo . --check
python3 "$(CUSTODIAN_SOURCE)/scripts/generate_staged_work_records.py" --repo . --repo-slug "$(GENERATED_REPO_SLUG)" --check

View file

@ -401,3 +401,9 @@ generated-work-records:
$(MAKE) --no-print-directory -f .generated-work-records.mk generated-work-records
generated-work-records-check:
$(MAKE) --no-print-directory -f .generated-work-records.mk generated-work-records-check
.PHONY: coordination-frame-staged coordination-frame-staged-check
coordination-frame-staged:
python3 tools/coordination-frame/staged.py --repo .
coordination-frame-staged-check:
python3 tools/coordination-frame/staged.py --repo . --check

View file

@ -244,3 +244,13 @@ WORK-RECORDS.md with the selected source paths. Existing generated-only guards
and exact-scope secret scans remain in place. This is repository maintenance,
not a runtime service, authorization decision, deployment or publication.
Other index generators and hub-derived briefs retain their owner boundaries.
## Coordination receiver generation — 2026-10-19
The shared opt-in commit recipe now compiles the local proposed Approval Engine
receiver from staged NetKingdom/coordination-engine/approval-engine sources,
then renders WORK-RECORDS. Dedicated Make generation/check targets preserve
unstaged authored work and refuse receiver conflicts or alternate-index writes.
Only the local receiver is staged; sibling sources and runtime registries are
untouched. Missing owner source checkouts fail closed. Multi-owner binding
publication and actual receiver activation retain their existing owner gates.

View file

@ -160,6 +160,7 @@
| task | NK-WP-0035-T04 | progress | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| task | NK-WP-0035-T05 | done | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| task | NK-WP-0035-T06 | done | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| task | NK-WP-0035-T07 | done | — | workplans/NK-WP-0035-emission-cadence-security-profile.md |
| task | NK-WP-0036-T01 | done | — | workplans/NK-WP-0036-restore-user-portal-client-registration.md |
| task | NK-WP-0036-T02 | done | — | workplans/NK-WP-0036-restore-user-portal-client-registration.md |
| task | NK-WP-0036-T03 | done | — | workplans/NK-WP-0036-restore-user-portal-client-registration.md |

View file

@ -40,3 +40,25 @@ This is the first rollout of a shared coordination setting. Other ecosystem
coordination consumers must adopt explicit frame multipliers through their own
configuration. Transport polling, security validity and credential leases are
not silently rescaled by a coordination setting.
## Staged local receiver generation
`make coordination-frame-staged` compiles and stages only the NetKingdom receiver
JSON from Git-index snapshots of its renderer, coordination-engine's frame/API
and approval-engine's cadence. It never edits those sibling checkouts. Unstaged
owner source edits are excluded, and unstaged receiver edits, missing/conflicted
or symlink inputs and alternate-index writes are refused. Stage the reviewed
adapter before its first run. `make coordination-frame-staged-check` is read-only.
The existing opt-in pre-commit recipe runs this adapter before WORK-RECORDS
rendering and the existing configured scanner. Missing sibling source checkouts
fail closed. Full multi-owner `render.py --write` remains an explicit preparation
command and is never run against real sibling worktrees by the commit hook.
`render.py --receiver-only` limits rendering to the one local proposed receiver;
none of these commands activate the receiver or publish runtime changes.
For a partial commit, generate from the selected staged sources first and include
the receiver with those sources; `git commit --only` cannot generate missing
changes into its temporary index. Cross-repo owner commits still need their own
review and publication; local index snapshots are source evidence, not release
or operational acceptance.

View file

@ -69,7 +69,27 @@ def bind_frame_annotation(text, digest, metadata_prefix):
return updated
def compile_bindings(workspace: Path):
def receiver_binding(frame, provenance, approval_text):
receiver = {
"status": "proposed-not-applied",
"coordination_frame": provenance,
"sender": "approval-engine",
"source": "approval-engine",
"tenant": "tenant:platform",
"may_read": False,
"emission_cadence_ref": "approval-engine/cadence.yaml",
"emission_cadence_sha256": hashlib.sha256(approval_text.encode()).hexdigest(),
"heartbeat_classes": {
name: frame.seconds(2) for name in ("issuance", "use", "supersession", "revocation")
},
"receiver_grace_factor": 1.5,
"effective_deadline_seconds": frame.seconds(3),
"preserve_existing_credentials_and_other_registry_fields": True,
}
return json.dumps(receiver, indent=2) + "\n"
def compile_bindings(workspace: Path, *, receiver_only=False):
frame = owner_frame(workspace)
digest = hashlib.sha256((workspace / FRAME_REF).read_bytes()).hexdigest()
provenance = {
@ -97,6 +117,9 @@ def compile_bindings(workspace: Path):
ext["effective_deadline_seconds"] = frame.seconds(3)
approval["extensions"].setdefault("netkingdom", {})["coordination_frame"] = provenance
outputs[approval_path] = json.dumps(approval, indent=2) + "\n"
if receiver_only:
path = workspace / "net-kingdom/generated/coordination-frame/approval-engine-receiver.json"
return frame, {path: receiver_binding(frame, provenance, outputs[approval_path])}
qonto_path = workspace / "qonto-assistant/specs/audit-emission-cadence.yaml"
qonto = yaml.safe_load(qonto_path.read_text())
@ -160,37 +183,20 @@ def compile_bindings(workspace: Path):
fixture["extensions"].setdefault("netkingdom", {})["coordination_frame"] = provenance
outputs[fixture_path] = json.dumps(fixture, indent=2) + "\n"
receiver = {
"status": "proposed-not-applied",
"coordination_frame": provenance,
"sender": "approval-engine",
"source": "approval-engine",
"tenant": "tenant:platform",
"may_read": False,
"emission_cadence_ref": "approval-engine/cadence.yaml",
"emission_cadence_sha256": hashlib.sha256(outputs[approval_path].encode()).hexdigest(),
"heartbeat_classes": {
name: frame.seconds(2) for name in ("issuance", "use", "supersession", "revocation")
},
"receiver_grace_factor": 1.5,
"effective_deadline_seconds": frame.seconds(3),
"preserve_existing_credentials_and_other_registry_fields": True,
}
outputs[
workspace / "net-kingdom/generated/coordination-frame/approval-engine-receiver.json"
] = json.dumps(receiver, indent=2) + "\n"
outputs[workspace / "net-kingdom/generated/coordination-frame/approval-engine-receiver.json"] = receiver_binding(frame, provenance, outputs[approval_path])
return frame, outputs
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--workspace", type=Path, default=ROOT.parent)
parser.add_argument("--receiver-only", action="store_true")
mode = parser.add_mutually_exclusive_group()
mode.add_argument("--check", action="store_true")
mode.add_argument("--write", action="store_true")
args = parser.parse_args(argv)
try:
frame, outputs = compile_bindings(args.workspace)
frame, outputs = compile_bindings(args.workspace, receiver_only=args.receiver_only)
changed = {
p: text for p, text in outputs.items() if not p.exists() or p.read_text() != text
}

View file

@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Stage only the local receiver from indexed owner sources; never mutate siblings."""
import argparse
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
OUTPUT = 'generated/coordination-frame/approval-engine-receiver.json'
SELF = 'tools/coordination-frame/staged.py'
INPUTS = {
'net-kingdom': ['tools/coordination-frame/render.py'],
'coordination-engine': ['config/coordination-frame.json', 'src/coordination_engine/timing.py'],
'approval-engine': ['cadence.yaml'],
}
def git(repo, *args, default_index=False):
env = dict(os.environ)
if default_index:
env.pop("GIT_INDEX_FILE", None)
return subprocess.check_output(['git', '-C', str(repo), *args], stderr=subprocess.PIPE, env=env)
def indexed(repo, path, *, default_index=False):
rows = git(repo, 'ls-files', '--stage', '--', path, default_index=default_index).decode().splitlines()
if len(rows) != 1:
raise ValueError('missing or conflicted source: ' + path)
metadata, name = rows[0].split('\t', 1)
mode, oid, stage = metadata.split()
if stage != '0' or mode not in ('100644', '100755') or name != path:
raise ValueError('nonregular or conflicted source: ' + path)
return git(repo, 'cat-file', 'blob', oid, default_index=default_index)
def generate(repo, *, check=False):
repo = Path(repo).resolve()
workspace = repo.parent
if Path(git(repo, 'rev-parse', '--show-toplevel').decode().strip()).resolve() != repo:
raise ValueError('repository root required')
if (repo / SELF).is_symlink() or (repo / SELF).read_bytes() != indexed(repo, SELF):
raise ValueError('stage the reviewed adapter before executing it')
target = repo / OUTPUT
if any(p.is_symlink() for p in (target, *target.parents)):
raise ValueError('redirected receiver output refused')
prior = indexed(repo, OUTPUT)
if target.read_bytes() != prior:
raise ValueError('unstaged receiver edits; preserve or reconcile first')
sources = []
with tempfile.TemporaryDirectory(prefix='coordination-staged-') as directory:
snapshot = Path(directory)
for slug, paths in INPUTS.items():
source_repo = repo if slug == 'net-kingdom' else workspace / slug
if Path(git(source_repo, 'rev-parse', '--show-toplevel').decode().strip()).resolve() != source_repo.resolve():
raise ValueError('owner repository root required')
for path in paths:
dest = snapshot / slug / path
dest.parent.mkdir(parents=True, exist_ok=True)
content = indexed(source_repo, path, default_index=source_repo != repo)
sources.append((source_repo, path, content))
dest.write_bytes(content)
subprocess.run([sys.executable, str(snapshot/'net-kingdom/tools/coordination-frame/render.py'),
'--workspace', str(snapshot), '--receiver-only', '--write'],
check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result = (snapshot/'net-kingdom'/OUTPUT).read_bytes()
if any(indexed(owner, name, default_index=owner != repo) != content
for owner, name, content in sources):
raise ValueError("owner index changed during compilation")
if result == prior:
return False
if check:
raise ValueError('staged receiver drift; run make coordination-frame-staged')
default_index = Path(git(repo, 'rev-parse', '--git-path', 'index', default_index=True).decode().strip())
if not default_index.is_absolute():
default_index = repo/default_index
active_index = Path(os.environ.get('GIT_INDEX_FILE', str(default_index)))
if not active_index.is_absolute():
active_index = repo/active_index
if active_index.resolve() != default_index.resolve():
raise ValueError('alternate-index mutation refused; generate before commit --only')
# Optimistic check: refuse if a source/index update raced compilation.
if indexed(repo, OUTPUT) != prior or target.read_bytes() != prior:
raise ValueError('receiver changed during compilation')
target.write_bytes(result)
git(repo, 'add', '--', OUTPUT)
return True
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--repo', type=Path, default=Path.cwd())
parser.add_argument('--check', action='store_true')
args = parser.parse_args()
try:
changed = generate(args.repo, check=args.check)
except (OSError, ValueError, subprocess.SubprocessError) as exc:
parser.exit(1, 'Coordination generation refused: ' + str(exc) + '\n')
print(json.dumps({'receiver_changed': changed, 'sibling_writes': False, 'live_operations': False}))

View file

@ -0,0 +1,85 @@
import importlib.util
import json
import os
from pathlib import Path
import subprocess
import pytest
ROOT = Path(__file__).resolve().parents[3]
SPEC = importlib.util.spec_from_file_location('staged_frame', ROOT/'tools/coordination-frame/staged.py')
staged = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(staged)
def git(repo, *args):
return subprocess.check_output(['git','-C',str(repo),*args])
@pytest.fixture
def repos(tmp_path):
workspace = ROOT.parent
for slug, names in staged.INPUTS.items():
repo=tmp_path/slug;repo.mkdir()
git(repo,'init','-q')
for name in names:
dest=repo/name;dest.parent.mkdir(parents=True,exist_ok=True)
dest.write_bytes((workspace/slug/name).read_bytes())
repo=tmp_path/'net-kingdom'
for name in (staged.SELF,staged.OUTPUT):
dest=repo/name;dest.parent.mkdir(parents=True,exist_ok=True)
dest.write_bytes((ROOT/name).read_bytes())
for slug in staged.INPUTS:git(tmp_path/slug,'add','.')
return tmp_path,repo
def test_receiver_only_ignores_unstaged_sibling_source(repos):
workspace,repo=repos
path=workspace/'coordination-engine/config/coordination-frame.json'
original=path.read_bytes();path.write_text('uncommitted invalid source')
assert staged.generate(repo) is False
assert path.read_text()=='uncommitted invalid source'
assert git(workspace/'coordination-engine','show',':config/coordination-frame.json')==original
def test_staged_frame_change_only_updates_local_receiver(repos):
workspace,repo=repos
path=workspace/'coordination-engine/config/coordination-frame.json'
data=json.loads(path.read_text());data['frame_seconds']=600;path.write_text(json.dumps(data))
git(path.parents[1],'add','config/coordination-frame.json')
approval=workspace/'approval-engine/cadence.yaml';before=approval.read_bytes()
with pytest.raises(ValueError,match='drift'):staged.generate(repo,check=True)
assert staged.generate(repo)
result=json.loads((repo/staged.OUTPUT).read_text())
assert result['effective_deadline_seconds']==1800
assert approval.read_bytes()==before
assert staged.generate(repo,check=True) is False
def test_unstaged_receiver_preserved(repos):
_,repo=repos;p=repo/staged.OUTPUT;p.write_text('local edit')
with pytest.raises(ValueError,match='unstaged receiver'):staged.generate(repo)
assert p.read_text()=='local edit'
def test_unstaged_adapter_refused(repos):
_,repo=repos;p=repo/staged.SELF;p.write_text(p.read_text()+'\n# local edit\n')
with pytest.raises(ValueError,match='stage the reviewed'):staged.generate(repo)
def test_symlink_source_refused(repos):
workspace,repo=repos;p=workspace/'approval-engine/cadence.yaml';p.unlink();p.symlink_to('/etc/passwd')
git(p.parent,'add','cadence.yaml')
with pytest.raises(ValueError,match='nonregular'):staged.generate(repo)
def test_alternate_index_write_refused_and_sibling_default_index_used(repos,monkeypatch):
workspace,repo=repos
path=workspace/'coordination-engine/config/coordination-frame.json'
data=json.loads(path.read_text());data['frame_seconds']=600;path.write_text(json.dumps(data))
git(path.parents[1],'add','config/coordination-frame.json')
alternate=repo/'.git/partial-index';alternate.write_bytes((repo/'.git/index').read_bytes())
before=(repo/staged.OUTPUT).read_bytes()
monkeypatch.setenv('GIT_INDEX_FILE',str(alternate))
with pytest.raises(ValueError,match='alternate-index mutation'):staged.generate(repo)
assert (repo/staged.OUTPUT).read_bytes()==before

View file

@ -10,7 +10,7 @@ owner: codex
topic_slug: netkingdom
planning_priority: P1
created: "2026-09-04"
updated: "2026-10-15"
updated: "2026-10-19"
related:
- GH-DEC-2026-004
- canon/standards/security-layer-model_v0.7.md
@ -417,3 +417,33 @@ and collector implementation are no longer missing. Remaining work is actual
elapsed-time capture, independent source/receiver correlation and observer
acceptance. Local-identity remains explicitly incompatible, with no completeness
claim. Detailed state: [owner acceptance plan](../docs/evidence/2026-10-15-cadence-adoption/owner-acceptance-plan.json).
## Automate staged receiver derivation without sibling mutations
```task
id: NK-WP-0035-T07
status: done
priority: medium
```
Completed source/fixture work 2026-10-19 for CUST-WP-0079-T05. The existing
coordination-frame compiler now has a receiver-only mode, and its staged
adapter compiles indexed NetKingdom renderer, coordination-engine frame/API and
approval-engine cadence inputs in an isolated temporary snapshot. Only the
local proposed receiver is written/staged. Dedicated Make targets and the
existing shared opt-in pre-commit recipe invoke this path; authored sibling
files and unstaged source edits are untouched. Missing/conflicted/nonregular
inputs, an unstaged receiver/adapter, changed input snapshots and required
alternate-index mutations refuse. The existing guard/scanner remain intact.
Eleven focused tests pass, including partial source staging, namespaced sibling
index isolation, receiver edit preservation and alternate-index refusal.
The first alternate-index test found that Git's default index query itself
honored GIT_INDEX_FILE; corrected the query and retained its regression. This
is a refined implementation, not an unchanged execution-success claim.
T06 retains ownership of the full multi-consumer frame compiler. T07 owns only
the staged local receiver automation introduced here. T04 retains registry,
collector and operational lifecycle/observer acceptance; neither this task nor
its tests claim runtime activation or a clean week of fleet commit history.
See tools/coordination-frame/README.md for commands and source boundaries.