docs: preserve reviewed NetKingdom session artifacts
This commit is contained in:
parent
c6410777ac
commit
6f29868361
50 changed files with 4632 additions and 0 deletions
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"status": "disproven-assumption",
|
||||
"original_source_commit": "5596185e71db4bff092b75af4d647e7501d47ff9",
|
||||
"evidence_tip": "a4e90c68604253b9284ba4f67c3f3cad5d80970a",
|
||||
"tree_identical": true,
|
||||
"default_rev_list_objects": 237,
|
||||
"original_commit_only_claim_false": true,
|
||||
"main_push_not_attempted": true,
|
||||
"all_ten_prior_receiver_results_retained": true
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"status": "fast-forwarded-locally",
|
||||
"old_commit": "5596185e71db4bff092b75af4d647e7501d47ff9",
|
||||
"new_commit": "b17e753efc8022fd482e3b2169ba3a604e4bb526",
|
||||
"same_tree": true,
|
||||
"original_commit_retained": true,
|
||||
"original_complete_index_entries_preserved": true,
|
||||
"unrelated_worktree_preserved": true,
|
||||
"force_or_history_rewrite": false
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
"""Real SOPS regressions with generated keys, no host custody or network mounts."""
|
||||
from pathlib import Path
|
||||
import fcntl, hashlib, json, os, resource, subprocess
|
||||
|
||||
BASE=Path(__file__).resolve().parent
|
||||
SOURCE=Path('/tmp/custodian-sops3133-fd/source')
|
||||
BINARY=Path('/tmp/custodian-sops3133-test/bin/sops')
|
||||
PIN='e5bec3346a873ae91d871550f3e698c1aad962aff462a080e40f25fde17fef6b'
|
||||
metadata=json.loads((BASE/'sops-source-tool.json').read_text())
|
||||
for relative,digest in metadata['public_source_files'].items():
|
||||
assert 'sha256:'+hashlib.sha256((SOURCE/relative).read_bytes()).hexdigest()==digest
|
||||
binary=BINARY.read_bytes();assert hashlib.sha256(binary).hexdigest()==PIN
|
||||
fd=os.memfd_create('custodian-generated-sops-test-binary',os.MFD_ALLOW_SEALING)
|
||||
os.write(fd,binary);os.lseek(fd,0,os.SEEK_SET)
|
||||
fcntl.fcntl(fd,fcntl.F_ADD_SEALS,fcntl.F_SEAL_WRITE|fcntl.F_SEAL_GROW|fcntl.F_SEAL_SHRINK|fcntl.F_SEAL_SEAL)
|
||||
script='''import json, os, pathlib, subprocess, sys
|
||||
assert not pathlib.Path('/home/worsch').exists()
|
||||
assert not pathlib.Path('/etc/rancher').exists()
|
||||
assert not pathlib.Path('/home/tester/.config').exists()
|
||||
assert not pathlib.Path('/proc/net/route').read_text().splitlines()[1:]
|
||||
assert os.stat('/tmp').st_uid==os.getuid()
|
||||
r=subprocess.run(['/usr/bin/python3','-m','pytest','-q','-c','/dev/null','-p','no:cacheprovider','--tb=no','--basetemp=/tmp/generated-sops-tests','/work/tests/test_sops_private.py','/work/tests/test_sops_write.py'],capture_output=True,text=True,timeout=150)
|
||||
# Pytest tracebacks/captured tool output are never emitted. Retain its closed
|
||||
# count summary only, including any failures rather than treating exit as proof.
|
||||
lines=[line for line in r.stdout.splitlines() if __import__('re').fullmatch(r'[0-9]+ (?:passed|failed|skipped|error|errors)(?:, [0-9]+ (?:passed|failed|skipped|error|errors))* in [0-9.]+s',line)]
|
||||
print(json.dumps({'status':'passed' if r.returncode==0 else 'failed','exit_code':r.returncode,'count_summary':lines,'private_diagnostics_suppressed':True,'generated_material_only':True,'host_custody_mounted':False,'network_routes_absent':True}))
|
||||
sys.exit(r.returncode)
|
||||
'''
|
||||
command=['/usr/bin/bwrap','--unshare-all','--die-with-parent','--new-session','--clearenv','--cap-drop','ALL',
|
||||
'--ro-bind','/usr','/usr','--symlink','usr/bin','/bin','--symlink','usr/lib','/lib',
|
||||
'--ro-bind-try','/lib64','/lib64','--proc','/proc','--dev','/dev','--dir','/etc',
|
||||
'--size','67108864','--tmpfs','/tmp','--tmpfs','/home','--dir','/home/tester',
|
||||
'--ro-bind',str(SOURCE),'/work','--dir','/tools','--perms','0500','--ro-bind-data',str(fd),'/tools/sops',
|
||||
'--setenv','PATH','/tools:/usr/bin:/bin','--setenv','HOME','/home/tester',
|
||||
'--setenv','LANG','C.UTF-8','--setenv','PYTHONDONTWRITEBYTECODE','1',
|
||||
'--setenv','PYTEST_DISABLE_PLUGIN_AUTOLOAD','1','--setenv','GOMAXPROCS','2',
|
||||
'--chdir','/work','--','/usr/bin/python3','-c',script]
|
||||
def limits():
|
||||
resource.setrlimit(resource.RLIMIT_CPU,(120,120))
|
||||
resource.setrlimit(resource.RLIMIT_AS,(8*1024**3,8*1024**3))
|
||||
# bwrap copies the sealed 52-MB tool into an anonymous read-only bind file.
|
||||
# The fixture's writable /tmp remains independently bounded to 64 MiB.
|
||||
resource.setrlimit(resource.RLIMIT_FSIZE,(64*1024**2,64*1024**2))
|
||||
try:
|
||||
result=subprocess.run(command,pass_fds=(fd,),capture_output=True,text=True,timeout=180,preexec_fn=limits)
|
||||
finally:os.close(fd)
|
||||
try:receipt=json.loads(result.stdout)
|
||||
except ValueError:receipt={'status':'unverified','exit_code':result.returncode,'fixed_reason':'namespace_preflight_or_runtime_failed','private_diagnostics_suppressed':True}
|
||||
receipt.update(binary_sha256='sha256:'+PIN,driver_sha256='sha256:'+hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
|
||||
namespace_teardown_complete=True,system_installation=False)
|
||||
(BASE/'sops-real-regressions.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print(json.dumps(receipt))
|
||||
raise SystemExit(result.returncode)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
Traceback (most recent call last):
|
||||
File "/home/worsch/repo-manager/.venv/bin/rmgr", line 8, in <module>
|
||||
sys.exit(main())
|
||||
^^^^^^
|
||||
File "/home/worsch/repo-manager/src/repo_manager/cli.py", line 853, in main
|
||||
result = sync_repository_projection(
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
File "/home/worsch/repo-manager/src/repo_manager/projection_sync.py", line 256, in sync_repository_projection
|
||||
_pending_path(repo_root).unlink(missing_ok=True)
|
||||
File "/usr/lib/python3.12/pathlib.py", line 1344, in unlink
|
||||
os.unlink(self)
|
||||
OSError: [Errno 30] Read-only file system: '/home/worsch/state-hub/.git/repo-manager/pending-work-record-projection.json'
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
# Configured-hook scanner unavailability fixture
|
||||
|
||||
Prepared against repo-manager `b02e1cefb0e07fe246dc9fe9c5a989fe06116587` to finish
|
||||
only RMGR-WP-0021-T05's actual configured-hook unavailability check. Source and
|
||||
remote receive/CI acceptance remain separate. Canonical files are unchanged by
|
||||
this packet. The coordinator executed the independently reviewed R3 runner
|
||||
successfully: ten unit checks, namespace hard-loss lifecycle and six actual
|
||||
configured-hook cases passed. The owner completion patch is prepared for review
|
||||
and has not been applied by this agent.
|
||||
|
||||
The existing root-owned non-setuid Bubblewrap is pinned by SHA256. The coordinator's
|
||||
prior disposable capability probe passed; see the adjacent
|
||||
`namespace-capability-sandbox.json`. No install, system setting, host scanner
|
||||
permission change or privileged host mutation is proposed.
|
||||
|
||||
`run_reviewed_fixture.sh` runs ten profile tests, a generated namespace hard-loss
|
||||
probe, then six actual configured-hook cases: clean, generated private key and
|
||||
scanner unavailable for the shared repo-manager and Custodian hooks. The exact
|
||||
files/hashes, expected results and resource bounds are in `manifest.json`.
|
||||
|
||||
The unavailable case overlays the exact admitted scanner pathname with a newly
|
||||
created empty 0400 regular file using a read-only namespace bind. Only that new
|
||||
empty fixture receives chmod; the actual scanner file is never modified. The
|
||||
canonical hook still calls the canonical source and exact configured scanner
|
||||
pathname. Namespace readback verifies the real scanner in both controls and the
|
||||
empty non-executable shadow in the unavailable case. Expected refusal is exit 2,
|
||||
`unverified`, `scanner-file-refused`, zero scanned blobs.
|
||||
|
||||
Every hook runs in fresh user, mount and network namespaces. Root is bound read
|
||||
only, and the original `/dev/shm` is bound writable over itself. The bootstrap
|
||||
checks root and `/dev/shm` device/inode identity, read-only/writable flags, UID/GID,
|
||||
changed user/mount/network namespaces, preserved PID namespace, canonical source,
|
||||
actual hook and `/usr/bin/python3` hashes before exec. It writes fixed metadata
|
||||
readback only into the private volatile fixture. Network namespace isolation
|
||||
adds no provider or network calls.
|
||||
|
||||
PID namespace isolation is deliberately absent. Destroying a PID namespace would
|
||||
also destroy the canonical scanner's independent cleanup observer before it can
|
||||
remove volatile files. Shared PID identity preserves the admitted pidfd and
|
||||
parent-death behavior. Bubblewrap's `--die-with-parent` bounds the hook command;
|
||||
canonical Git/scanner children use parent-death SIGKILL. The independent watchdog
|
||||
survives source-parent death and performs guarded cleanup. The outer fixture also
|
||||
uses the canonical private-tmpfs watchdog, registered before generating bytes.
|
||||
|
||||
`probe_namespace_lifecycle.py` tests this interaction before any actual hook
|
||||
case. It creates a disposable spawner, namespace inner process and canonical
|
||||
cleanup observer, writes a public generated sentinel, publishes atomic early PID readiness before source storage, waits for the
|
||||
coordinator to register exact Bubblewrap/inner pidfds and release an exact generated
|
||||
gate, then creates source storage and kills only the new spawner. A missing gate
|
||||
refuses storage after eight seconds. Acceptance requires proven death of the spawner,
|
||||
Bubblewrap, namespace inner process and cleanup observer, and disappearance of
|
||||
inner and outer volatile directories. Exceptional cleanup quiesces known source
|
||||
and readback creators before removing their outer directory. It never kills the
|
||||
calling shell/tool/harness or an untracked process group. If exact death cannot
|
||||
be established, the observer retains that duty rather than deleting under a known
|
||||
live creator.
|
||||
|
||||
The hook fixture uses the prior reviewed alternate-index/object-store method.
|
||||
HEAD tree metadata seeds a temporary index; only one newly generated fixture
|
||||
blob may differ. Existing blob contents and the real index are not opened. The
|
||||
real index is checked by stat identity only. Git's fsmonitor, split-index,
|
||||
sparse-checkout and untracked-cache options are disabled only in the fixture
|
||||
process environment. No Git refs, worktree source, hooks, source code, scanner
|
||||
bytes/mode or persistent configuration changes. The only persistent writes are
|
||||
requested fixed-metadata receipts in this packet. Readbacks and raw hook output
|
||||
are not retained if they contain unexpected fields or diagnostics.
|
||||
|
||||
The original configured-hook clean/key receipt remains unchanged. The owner file still records T05 progress until the coordinator applies and
|
||||
publishes the prepared completion patch; the bounded runtime receipts now verify
|
||||
the fixture. No whole TSA15 closure follows.
|
||||
|
||||
The independently reviewed but superseded lifecycle revision is retained as
|
||||
`lifecycle-rev1-superseded.py` (never executed). `lifecycle-gate.patch` records
|
||||
the pre-effect registration correction. Seven unit checks include refusal of
|
||||
source storage when coordinator gate release never arrives.
|
||||
|
||||
|
||||
R1 coordinator execution failed after early registration with
|
||||
`namespace-lifecycle-preflight-failed`; it did not run actual hook cases. Both
|
||||
reported storage directories were removed. Its raw driver, manifest and receipt
|
||||
are retained in `failed-r1/`. R1 discarded Bubblewrap/spawner stderr and that lost
|
||||
diagnostic history cannot be reconstructed.
|
||||
|
||||
R2 was prepared with the following diagnostic changes; its later failed
|
||||
execution is retained below. `lifecycle-r2.patch` atomically publishes the release gate
|
||||
(previously it could be observed empty before buffered token write/close). This
|
||||
is a possible cause, not a verified explanation. R2 adds fixed public phase
|
||||
readback and class/errno/whitelisted source refusal codes. It captures up to
|
||||
8192 bytes each of actual Bubblewrap/spawner diagnostics, with 1 MiB child file
|
||||
limits. This lifecycle probe processes only a public generated sentinel; it has
|
||||
no key, user index or user source blob input. Arbitrary exception messages are
|
||||
excluded from phase metadata. Eight unit checks pass, including that exclusion.
|
||||
The retained R2 receipt records the later diagnostic execution.
|
||||
|
||||
|
||||
R2 coordinator execution again refused: the fixed phase reported
|
||||
`PermissionError`, errno 13; both captured stderr streams were empty and fixture
|
||||
cleanup was verified. That record is retained in `failed-r2/`. Because the error
|
||||
phase overwrote its previous stage, the `/dev/null` explanation remained an
|
||||
unverified hypothesis.
|
||||
|
||||
R3 was subsequently reviewed and executed successfully, as recorded in the
|
||||
current receipts. `namespace-r3.patch` adds the narrow
|
||||
`--dev-bind /dev/null /dev/null` exception; no other host device is exposed and
|
||||
root remains read only. Both lifecycle and hook bootstrap verify that it is the
|
||||
same host character device (device/inode/rdev) and actually open it O_RDWR with
|
||||
O_NOFOLLOW. Error phases now retain the last successful phase and include an
|
||||
exception filename only if it is literally `/dev/null`. Ten unit checks pass,
|
||||
including rejection of a wrong null identity before opening it. The final runtime
|
||||
record verifies this profile; the earlier cause is not retroactively asserted.
|
||||
|
||||
|
||||
Current delivery: `namespace-lifecycle-results.json` verifies exact disposable
|
||||
parent loss, Bubblewrap/inner/cleanup-observer death and removed inner/outer
|
||||
storage. `namespace-hook-results.json` verifies clean (exit 0), generated-key
|
||||
blocked (exit 1), and exact non-executable scanner-shadow refusal (exit 2,
|
||||
`scanner-file-refused`, zero scanned blobs) for both actual hooks. Host scanner
|
||||
stat/hash/mode, hook/source routing, real index stat identities and HEADs stayed
|
||||
unchanged; all new volatile storage was absent. All six namespace readbacks
|
||||
verified root/shm and UID/GID identity, canonical source/hook/interpreter hashes
|
||||
and the null character-device identity/open.
|
||||
|
||||
`owner-T05-completion.patch` and `RMGR-WP-0021.completed-proposal.md` are exact
|
||||
reviewable results against owner commit `b02e1cefb0e07fe246dc9fe9c5a989fe06116587`.
|
||||
Only T05 changes progress→done; T03 progress, T04 todo and active workplan status
|
||||
are preserved. R1/R2 failure history and the bounded namespace-only interpretation
|
||||
are explicit. Source publication and the accepted explicit repo-local CLI route
|
||||
remain earlier delivery evidence; no global CLI installation or remote receive,
|
||||
CI, response or whole-TSA15 acceptance is claimed.
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
---
|
||||
id: RMGR-WP-0021
|
||||
type: workplan
|
||||
title: "Secret-detection adapter and repository enforcement acceptance"
|
||||
domain: infotech
|
||||
repo: repo-manager
|
||||
status: active
|
||||
owner: codex
|
||||
topic_slug: repo-manager
|
||||
created: "2026-10-12"
|
||||
updated: "2026-10-17"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "5aa6f84a-c682-5996-ae26-96daef670833"
|
||||
---
|
||||
|
||||
Native repository-control work for CUST-WP-0080 G07 / DSM-DETECT. Existing
|
||||
RMGR-WP-0009 provenance remains separate and nonblocking by its contract.
|
||||
Detection is an additional security check; this plan does not redefine provenance,
|
||||
credential custody, PDP authority, Forge ownership or incident authorization.
|
||||
|
||||
## Provide a bounded content-silent staged-object scanner adapter
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0021-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "d3d20c18-5a5f-5f8a-b5c4-a22b4b662ebe"
|
||||
```
|
||||
|
||||
2026-10-12: all 21 reviewed estate checkouts resolve their pre-commit hook to the
|
||||
same generated-only guard. A disposable valid private key was committed through
|
||||
that inherited hook, proving no local prevention for that fixture. No real
|
||||
credential was used. The new `rmgr secret-scan` adapter pins Gitleaks 8.30.1 Linux
|
||||
x64, reads complete changed index blobs, isolates configuration, suppresses raw
|
||||
output, verifies index stability and refuses unknown binaries, oversized inputs,
|
||||
changed gitlinks and other unverified cases. Real scanner tests prove refusal of
|
||||
a disposable private-key commit, partial/alternate-index correctness, local
|
||||
configuration/inline suppression refusal and Git replacement-object resistance.
|
||||
Evidence: Custodian `docs/evidence/2026-10-12-netkingdom-security/secret-detection.json`.
|
||||
This is source/tool behavior, not workstation or forge activation.
|
||||
|
||||
## Admit the tool and activate local prevention for the reviewed repositories
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0021-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "e27817e6-b298-581f-b99a-05ec459be749"
|
||||
```
|
||||
|
||||
Review durable executable installation/version custody and exact repository
|
||||
coverage. Preserve existing shared generated-only and provenance hooks and the
|
||||
Custodian's separate local hook. Do not replace unknown hooks, alter global
|
||||
routing or silently affect unreviewed repositories. Test actual configured hooks
|
||||
with clean/disposable-secret/scanner-failure cases without changing production
|
||||
source or committing a credential. Record observed coverage and explicit
|
||||
uncovered checkouts. Installer rollback restores exact prior content/routing.
|
||||
|
||||
2026-10-12: activated the reviewed composition in the existing shared hook and
|
||||
Custodian local hook, with Gitleaks 8.30.1 admitted at
|
||||
`/home/worsch/.local/lib/repo-manager/gitleaks/8.30.1/gitleaks` under the pinned
|
||||
SHA256. No Git routing or other hook changed. All 22 exact workstation roots in
|
||||
the proposal passed actual configured-hook checks: clean exit 0, disposable
|
||||
private key exit 1, generated-only exit 1, non-executable scanner exit 2. Scanner
|
||||
mode was restored; a checkout outside the exact scope retained its prior behavior.
|
||||
Probes used temporary alternate indexes/object stores, without changing user
|
||||
source, staged content or branches. Eleven activation/rollback/guard tests pass.
|
||||
Custodian evidence: `secret-hook-activation.json`; reviewed proposal and rollback
|
||||
executor: `docs/changes/CUST-WP-0080/`. This covers these workstation checkouts,
|
||||
not other machines/clones, existing history, remote enforcement or real findings.
|
||||
T03/T04 remain open; local hooks remain bypassable.
|
||||
|
||||
## Establish independent forge and CI detection coverage
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0021-T03
|
||||
status: progress
|
||||
priority: high
|
||||
state_hub_task_id: "0ab44bd9-00d8-5470-ad45-7350e7868d0a"
|
||||
```
|
||||
|
||||
Under CUST-WP-0080 coordination with railiance-forge, inspect existing server-side
|
||||
and required-check controls; define the production repository denominator and
|
||||
owner-native enforcement record before activating changes. Cover introduced
|
||||
history/merge cases and bypasses, missing scanner, malformed input and scanner
|
||||
failure. Record actual rejected/accepted push or required-check receipts. Local
|
||||
hook success is insufficient and this staged adapter alone does not scan history.
|
||||
|
||||
2026-10-12 read-only native baseline: railiance01 runs Forgejo 11.0.3.
|
||||
All 22 reviewed repositories have only the upstream-generated receive-hook files,
|
||||
matched by SHA256 to the v11.0.3 templates; no custom scanner hook is installed at
|
||||
that boundary. Storage contains 165 repository directories, which still need
|
||||
classification before being an admitted production denominator. Independent
|
||||
Actions/branch-protection coverage and push acceptance remain unverified. Do not
|
||||
replace generated dispatchers or enable arbitrary user hook editing to add the
|
||||
control. Receipt: Custodian `forge-secret-detection-baseline.json`. No live
|
||||
server mutation, credential access or synthetic push was performed.
|
||||
|
||||
2026-10-12 implementation handoff: FORGE-WP-0006 owns the independent receive
|
||||
boundary. Its introduced-history adapter passes 33 leaf cases with actual
|
||||
disposable pushes, including added-then-deleted/merge/message/tag secrets and
|
||||
scanner failures. Source is not installed server enforcement. Native inventory,
|
||||
exact-artifact custody, SSH/HTTP/frontend/restart/new-repository acceptance and
|
||||
CI authority remain open in FORGE-WP-0006-T02/T03. Custodian receipt:
|
||||
`docs/evidence/2026-10-12-netkingdom-security/receive-secret-detection.json`.
|
||||
|
||||
2026-10-13 receive lifecycle acceptance now exists in FORGE-WP-0006: 22 server
|
||||
profiles, template inheritance and repaired scanner custody across two actual
|
||||
Kubernetes replacements. The original failed rollout is preserved in Custodian's
|
||||
`forge-custody-recovery-summary.json`; 144 of 166 repositories and the remaining
|
||||
HTTP/SSO/frontend/CI/response acceptance stay open. The server scanner's 90-second
|
||||
budget exceeded State Hub's 30-second and Repo Manager's 60-second push deadlines.
|
||||
Both clients now allow 120 seconds; a timeout reports unverified remote outcome
|
||||
and requires ref inspection before retry. No force push or scanner exception is
|
||||
introduced. State Hub owner record: STATE-WP-ADHOC-2026-10-13-T01. Dedicated timeout
|
||||
regressions and real delayed-hook acceptance are recorded in Custodian's
|
||||
`push-deadline.json`. This transport correction does not close T03.
|
||||
|
||||
2026-10-13 expansion completed: FORGE6-EXPAND-001 revision 1 exited 0 after
|
||||
all 143 targets passed actual SSH clean/scanner-refusal probes under an exact-ref
|
||||
veto. The independent final read-only audit reconciles 167 repositories: 166
|
||||
admitted profiles, one builtin-only (numeric ID 55), zero unknown profiles. All
|
||||
23 baseline profiles remain exact; no probe refs, temporary vetoes or ownership
|
||||
markers remain. Each rejected synthetic commit was absent after its probe.
|
||||
Repository 55 has four external submodules and remains required compatibility
|
||||
work. Ordinary source acceptance is distinct from vetoed scanner checks.
|
||||
Production HTTP/API/frontend acceptance remains unverified: the protected API
|
||||
credential route failed before child handoff and the metadata-only capability
|
||||
check denied access. Prepare one complete acceptance command for the existing
|
||||
attended login window. CI, import/mirror, historical/LFS/log/artifact detection
|
||||
and private response gates remain open; this is no whole-estate closure.
|
||||
Evidence: Custodian `docs/evidence/2026-10-13-netkingdom-security/forge-expansion-final.json`
|
||||
and `forge-api-route-readiness.json`.
|
||||
|
||||
## Verify private finding-to-response handling
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0021-T04
|
||||
status: todo
|
||||
priority: high
|
||||
state_hub_task_id: "44272818-db49-58a2-83b1-d2f9e9d5976c"
|
||||
```
|
||||
|
||||
Exercise a synthetic finding through metadata-only owner routing and approved
|
||||
private review. Define exact false-positive exception governance; require actual
|
||||
approval, scope, expiry/review and regression coverage. For genuine exposed
|
||||
credentials, retain incident owner and revoke/replace/adopt/validate gates. Do not
|
||||
execute founder-deferred rotations or treat suppressed output as remediation.
|
||||
Close only after native coverage and response evidence are adequate for the
|
||||
applicable DSM-DETECT requirements; retain residuals as live work records.
|
||||
|
||||
|
||||
## Publish measured staged-scanner batching and verify runtime routing
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0021-T05
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "25fbe694-41d8-5232-bce0-4ed362387d5a"
|
||||
```
|
||||
|
||||
2026-10-17 prepared refinement for the local staged-object adapter. Independent
|
||||
review and a coordinator-executed generated-fixture run cover the exact source
|
||||
SHA256 `e709c658f779534fcab093207b0a78e49d54fc584c515fdf7ac535ce75f6c400`:
|
||||
42 tests, 13 exact old/new summary parity cases, immutable scanner snapshot,
|
||||
volatile-only bounded batches, watchdog and parent-loss refusal/cleanup. Warm
|
||||
p50 for 1/16/64 distinct 1 KiB blobs is 0.835/0.884/0.904 seconds, compared with
|
||||
0.702/12.065/49.172 seconds in the retained baseline. The one-blob case regressed;
|
||||
this is descriptive local fixture evidence, not aggregate workload or tail
|
||||
acceptance. Larger blobs retain the original stdin route.
|
||||
|
||||
Publish the reviewed patch against the exact owner base, verify the configured
|
||||
shared and Custodian hooks still resolve to the admitted owner source, then run
|
||||
clean/private-key/scanner-unavailable generated alternate-index probes without
|
||||
reading or changing user source/indexes. Verify actual CLI import routing; the
|
||||
repo-local `.venv/bin/rmgr` imports owner source, but the previously advertised
|
||||
`/home/worsch/.local/bin/rmgr` is absent in the read-only observation and must not
|
||||
be described as installed operational coverage. Reconcile that exact CLI route
|
||||
before claiming CLI publication. Preserve existing hook guards and routing;
|
||||
retain rollback to the exact prior adapter bytes. Source publication and runtime
|
||||
acceptance must have separate receipts.
|
||||
|
||||
Review packet: Custodian
|
||||
`docs/changes/netkingdom-target-2026-10-16/scanner-batch-r1/`.
|
||||
No canonical owner mutation or runtime admission has occurred in the packet.
|
||||
This task does not close T03/T04, independent receive/CI coverage, source-control
|
||||
bypass limits or whole TSA15.
|
||||
|
||||
2026-10-17 integration started: exact reviewed source is applied in the owner
|
||||
checkout. Canonical tests, configured-hook fixtures and publication are being
|
||||
verified separately; actual-hook scanner-unavailability remains pending.
|
||||
|
||||
2026-10-17 verified source follow-through: source commit `83c521b` passed normal
|
||||
local scanning and all 42 canonical scanner tests. The exact final tmpfs candidate
|
||||
also passed 13 old/new parity cases. The actual configured shared and Custodian
|
||||
hooks each accepted a clean generated fixture and blocked a generated private key,
|
||||
using alternate indexes/object stores in private tmpfs; the real indexes were not
|
||||
opened and their stat identities and HEADs stayed unchanged. All new fixture and
|
||||
scan storage was removed. Scanner/hook/source bytes and routing stayed exact.
|
||||
No chmod or unavailable-scanner injection touched the real scanner. Source tests
|
||||
cover unavailable/wrong-digest refusal, but actual configured-hook unavailability
|
||||
is still pending, so T05 stays progress. The explicit repo-local CLI published
|
||||
through rmgr sync --push and central primary/railiance01 accepted source projection;
|
||||
no global installed CLI is claimed. T03/T04 and whole target gates remain open.
|
||||
Final coordinator evidence: resume-integration-r1/publication-verification.json.
|
||||
|
||||
|
||||
2026-10-17 bounded T05 completion evidence: the independently reviewed R3 namespace
|
||||
fixture passed ten profile checks, exact-pidfd hard-parent-loss and cleanup
|
||||
acceptance, and six actual configured-hook cases. Both the shared repo-manager
|
||||
hook and Custodian hook accepted a clean generated blob (exit 0), blocked a
|
||||
freshly generated private-key fixture (exit 1), and refused a non-executable
|
||||
scanner view (exit 2, `unverified`, `scanner-file-refused`, zero scanned blobs).
|
||||
The unavailable view was a new empty 0400 file read-only-bound over the exact
|
||||
configured scanner path inside fresh user/mount/network namespaces; the host
|
||||
scanner's bytes, stat identity and mode were unchanged. The canonical source,
|
||||
hooks and interpreter hashes were read back before exec, original root and
|
||||
`/dev/shm` device/inode identities and UID/GID were preserved, root was read only,
|
||||
and only original `/dev/shm` and exact `/dev/null` had the reviewed writable/device
|
||||
bind exceptions. The null character device and actual O_RDWR open were verified.
|
||||
PID namespace identity stayed shared so the canonical independent cleanup
|
||||
observer survived parent loss. Real indexes were not opened, real index stat
|
||||
identities and HEADs stayed unchanged, and all new volatile storage was absent.
|
||||
|
||||
Retain the failed preparation history: R1 refused after early process
|
||||
registration without a full readback; R2 recorded PermissionError errno 13,
|
||||
without enough retained phase information to establish its exact cause. Both
|
||||
failed revisions and receipts are in `failed-r1/` and `failed-r2/`. R3 atomically
|
||||
published the release gate, retained fixed public phase diagnostics and verified
|
||||
the narrow `/dev/null` device bind and open. Its successful result does not
|
||||
retroactively assign a verified cause or success to R1/R2.
|
||||
|
||||
Receipts: Custodian
|
||||
`docs/changes/netkingdom-target-2026-10-17/scanner-unavailable-r1/namespace-lifecycle-results.json`
|
||||
and `namespace-hook-results.json`, with exact driver hashes and manifest.
|
||||
Earlier publication and explicit repo-local CLI acceptance remain recorded above;
|
||||
no globally installed CLI is claimed. This completes the bounded local staged
|
||||
scanner optimization and its configured-hook generated-fixture acceptance. It
|
||||
proves the exact non-executable scanner view within this namespace fixture, not
|
||||
an observed host outage or coverage of other machines. T03 remains progress,
|
||||
T04 remains todo, the workplan remains active, and remote receive/CI, broader
|
||||
source-control coverage, real finding response and whole TSA15 remain open.
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"status": "unverified", "fixture_removed": true, "inner_storage_removed": true}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"state": "prepared; independent root review and coordinator execution pending",
|
||||
"owner_head": "b02e1cefb0e07fe246dc9fe9c5a989fe06116587",
|
||||
"source_sha256": "e709c658f779534fcab093207b0a78e49d54fc584c515fdf7ac535ce75f6c400",
|
||||
"scanner_sha256": "88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509",
|
||||
"hook_sha256": "d50cf763dc9814383c6886e30b6650bbbdcb49ddd78889082e3fda3626a2cbfd",
|
||||
"bwrap_sha256": "e318903862396f96de3df57264e0158682b952fd3fb53ac23d876413e7b30f71",
|
||||
"files": {
|
||||
"probe_namespace_hooks.py": "0e680c9ae85242bb510cbe871e14c498e01643eccc3656533e6fb2902030c2d8",
|
||||
"probe_namespace_lifecycle.py": "c46efb37e02349ba73f83b46c8fd8b1abc286282be8dc0a105ae76911e1ca266",
|
||||
"test_namespace_profile.py": "5571d9f04065fb102236f058bf7ae2e2d62c1ea0b28f13c3585c0e38cdbc6d09",
|
||||
"run_reviewed_fixture.sh": "07d0b518a4e99c6d88c0cbc2a03c76b20429f0ac7d86d3985ca46528cbffe596"
|
||||
},
|
||||
"expected_hook_cases": 6,
|
||||
"unit_profile_checks_passed": 7,
|
||||
"namespace_policy": {
|
||||
"changed": [
|
||||
"user",
|
||||
"mount",
|
||||
"network"
|
||||
],
|
||||
"preserved": [
|
||||
"pid",
|
||||
"root dev/inode",
|
||||
"dev/shm dev/inode",
|
||||
"uid/gid"
|
||||
],
|
||||
"writable_bind_only": "/dev/shm to existing /dev/shm",
|
||||
"scanner_shadow": "new generated empty0400file readonlybound over exact admitted scanner path only for unavailable cases"
|
||||
},
|
||||
"expected_unavailable_result": {
|
||||
"status": "unverified",
|
||||
"exit_code": 2,
|
||||
"reason": "scanner-file-refused",
|
||||
"scanned_blobs": 0
|
||||
},
|
||||
"forbidden_actions": [
|
||||
"host scanner chmod/bytes changes",
|
||||
"hook/source/config changes",
|
||||
"real index open/write",
|
||||
"real source blob scan",
|
||||
"network/provider calls",
|
||||
"new install",
|
||||
"production configuration",
|
||||
"external messages"
|
||||
],
|
||||
"cleanup": "reviewed canonical tmpfs+pidfd watchdog; bwrapdie-with-parent; source childrenPDEATHSIG; actual namespace hard-loss fixture first, exact disposable spawner only, independent cleanupobserver survives",
|
||||
"bounds": {
|
||||
"git_seconds": 20,
|
||||
"hook_case_seconds": 30,
|
||||
"child_cpu_seconds": 20,
|
||||
"child_virtual_address_GiB": 8,
|
||||
"generated_git_file_limit_MiB": 16,
|
||||
"bwrap_hook_file_limit_MiB": 64,
|
||||
"outer_watchdog_seconds": 180,
|
||||
"watchdog_TERM_grace_seconds": 5,
|
||||
"uncertain_kernel_quiescence": "retain observer duty until exact pidfddeath; never delete under known live creator"
|
||||
},
|
||||
"canonical_mutations_performed": false,
|
||||
"runtime_fixture_executed": false,
|
||||
"registration_gate": "atomic inner PID+namespace early-ready; main opens exact bwrap+inner pidfds and verifies live before writing exact generated gate; inner refuses storage after8seconds without release",
|
||||
"superseded_lifecycle_sha256": "7b74d96c867d44a0e994890d58d0d039028684ded3611ffb65f9c7b09d6f5527"
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"status": "unverified",
|
||||
"scope": "generated namespace hard-parent-loss process tree only",
|
||||
"real_index_opened": false,
|
||||
"real_credentials_read": false,
|
||||
"provider_calls": 0,
|
||||
"host_scanner_modified": false,
|
||||
"reason": "namespace-lifecycle-preflight-failed",
|
||||
"fixture_removed": true,
|
||||
"inner_storage_removed": true,
|
||||
"driver_sha256": "c46efb37e02349ba73f83b46c8fd8b1abc286282be8dc0a105ae76911e1ca266"
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Hard-loss fixture targets only a newly spawned generated-data process tree."""
|
||||
import argparse
|
||||
import functools
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import select
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
spec = importlib.util.spec_from_file_location('namespace_profile', HERE/'probe_namespace_hooks.py')
|
||||
p = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(p)
|
||||
|
||||
|
||||
def adapter():
|
||||
return p.load_bound(p.SOURCE, p.SOURCE_SHA, 'reviewed_lifecycle_storage')
|
||||
|
||||
|
||||
def inner(path):
|
||||
data = json.loads(path.read_text())
|
||||
current = p.namespaces()
|
||||
if (p.identity(Path('/')) != data['root'] or p.identity(Path('/dev/shm')) != data['shm']
|
||||
or current['pid'] != data['ns']['pid'] or current['mnt'] == data['ns']['mnt']
|
||||
or current['user'] == data['ns']['user'] or current['net'] == data['ns']['net']
|
||||
or p.sha(Path('/usr/bin/python3')) != data['interpreter_sha256']):
|
||||
return 23
|
||||
# Register the exact inner process before any source-bearing storage can
|
||||
# exist. Publication is atomic; the only earlier effect is PID metadata.
|
||||
early = Path(data['early_ready'])
|
||||
temporary = early.with_suffix('.tmp')
|
||||
temporary.write_text(json.dumps({'inner_pid':os.getpid(), 'namespace_identity_verified':True}))
|
||||
temporary.replace(early)
|
||||
until = time.monotonic() + 8
|
||||
gate = Path(data['release_gate'])
|
||||
while not gate.exists() and time.monotonic() < until:
|
||||
time.sleep(.01)
|
||||
if not gate.exists() or gate.read_text() != data['gate_token']:
|
||||
return 25
|
||||
a = adapter()
|
||||
with a.private_storage() as (area, watcher):
|
||||
(area/'public-generated-sentinel').write_bytes(b'public generated parent-loss sentinel')
|
||||
readback = {'storage':str(area), 'storage_identity':p.identity(area),
|
||||
'inner_pid':os.getpid(), 'watcher_pid':watcher.pid,
|
||||
'namespace_identity_verified':True}
|
||||
temporary = Path(data['readback'] + '.tmp')
|
||||
temporary.write_text(json.dumps(readback))
|
||||
temporary.replace(Path(data['readback']))
|
||||
time.sleep(30)
|
||||
return 24 # The observer must kill the disposable spawner before normal expiry.
|
||||
|
||||
|
||||
def spawn(path):
|
||||
a = adapter()
|
||||
data = json.loads(path.read_text())
|
||||
env = {'PATH':'/usr/bin:/bin','HOME':'/home/worsch','LANG':'C'}
|
||||
args = p.command(Path('/home/worsch/repo-manager'), env, path)
|
||||
args = args[:args.index('--')+1] + ['/usr/bin/python3','-I',str(Path(__file__).resolve()),'--inner',str(path)]
|
||||
child = subprocess.Popen(args, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
preexec_fn=functools.partial(a.child_limits,64*1024**2,os.getpid()))
|
||||
try:
|
||||
temporary = Path(data['bwrap_pid'] + '.tmp')
|
||||
temporary.write_text(str(child.pid))
|
||||
temporary.replace(Path(data['bwrap_pid']))
|
||||
return child.wait(timeout=30)
|
||||
finally:
|
||||
if child.poll() is None:
|
||||
child.kill(); child.wait(timeout=5)
|
||||
|
||||
|
||||
def main():
|
||||
receipt={'status':'unverified','scope':'generated namespace hard-parent-loss process tree only',
|
||||
'real_index_opened':False,'real_credentials_read':False,'provider_calls':0,
|
||||
'host_scanner_modified':False}
|
||||
descriptors={}; child=None; fixture=None; inner_area=None
|
||||
try:
|
||||
if p.sha(p.BWRAP) != p.BWRAP_SHA:
|
||||
raise p.Refused('bwrap-drift')
|
||||
a=adapter()
|
||||
previous=set(Path('/dev/shm').glob('rmgr-secret-scan-*'))
|
||||
with a.private_storage() as (fixture, outer_watcher):
|
||||
data={'root':p.identity(Path('/')),'shm':p.identity(Path('/dev/shm')),
|
||||
'ns':p.namespaces(),'readback':str(fixture/'inner.readback'),
|
||||
'bwrap_pid':str(fixture/'bwrap.pid'),
|
||||
'early_ready':str(fixture/'inner.early.ready'),
|
||||
'release_gate':str(fixture/'release.gate'), 'gate_token':os.urandom(16).hex(),
|
||||
'interpreter_sha256':p.sha(Path('/usr/bin/python3'))}
|
||||
path=fixture/'spec.json'; path.write_text(json.dumps(data))
|
||||
child=subprocess.Popen([sys.executable,'-I',str(Path(__file__).resolve()),'--spawn',str(path)],
|
||||
env={'PATH':'/usr/bin:/bin','HOME':'/home/worsch','LANG':'C'},
|
||||
stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,
|
||||
preexec_fn=functools.partial(a.child_limits,64*1024**2,os.getpid()))
|
||||
try:
|
||||
descriptors['spawner']=os.pidfd_open(child.pid)
|
||||
# Wait for atomic pre-effect readiness, register both exact
|
||||
# process identities, then release only this generated gate.
|
||||
until=time.monotonic()+8
|
||||
early=Path(data['early_ready']); bwrap_pid=Path(data['bwrap_pid'])
|
||||
while (not early.exists() or not bwrap_pid.exists()) and child.poll() is None and time.monotonic()<until:
|
||||
time.sleep(.01)
|
||||
if not early.exists() or not bwrap_pid.exists():
|
||||
raise p.Refused('namespace-early-registration-failed')
|
||||
ready=json.loads(early.read_text())
|
||||
if set(ready) != {'inner_pid','namespace_identity_verified'} or not ready['namespace_identity_verified']:
|
||||
raise p.Refused('namespace-early-readiness-refused')
|
||||
descriptors['bwrap']=os.pidfd_open(int(bwrap_pid.read_text()))
|
||||
descriptors['inner']=os.pidfd_open(ready['inner_pid'])
|
||||
for descriptor in (descriptors['bwrap'],descriptors['inner']):
|
||||
dead,_,_=select.select([descriptor],[],[],0)
|
||||
if dead:
|
||||
raise p.Refused('namespace-creator-died-before-release')
|
||||
with Path(data['release_gate']).open('x') as gate:
|
||||
gate.write(data['gate_token'])
|
||||
until=time.monotonic()+8
|
||||
readback=Path(data['readback'])
|
||||
while not readback.exists() and child.poll() is None and time.monotonic()<until:
|
||||
time.sleep(.02)
|
||||
if not readback.exists():
|
||||
raise p.Refused('namespace-lifecycle-preflight-failed')
|
||||
body=json.loads(readback.read_text()); inner_area=Path(body['storage'])
|
||||
if (not body['namespace_identity_verified'] or p.identity(inner_area)!=body['storage_identity']
|
||||
or not str(inner_area).startswith('/dev/shm/rmgr-secret-scan-')):
|
||||
raise p.Refused('lifecycle-readback-refused')
|
||||
if body['inner_pid'] != ready['inner_pid']:
|
||||
raise p.Refused('namespace-inner-identity-changed')
|
||||
descriptors['cleanup_observer']=os.pidfd_open(body['watcher_pid'])
|
||||
# Exactly this new disposable spawner is killed. The calling tool,
|
||||
# shell, test harness, observer and their process groups are untouched.
|
||||
signal.pidfd_send_signal(descriptors['spawner'],signal.SIGKILL)
|
||||
child.wait(timeout=5)
|
||||
until=time.monotonic()+8
|
||||
pending=set(descriptors.values())
|
||||
while pending and time.monotonic()<until:
|
||||
ready,_,_=select.select(list(pending),[],[],max(0,until-time.monotonic()))
|
||||
pending.difference_update(ready)
|
||||
if pending or inner_area.exists():
|
||||
raise p.Refused('namespace-hard-loss-cleanup-unverified')
|
||||
receipt.update(namespace_identity_verified=True, pre_effect_pidfd_registration_verified=True,
|
||||
exact_disposable_parent_killed=True,
|
||||
bwrap_inner_and_cleanup_observer_death_verified=True,inner_storage_removed=True)
|
||||
finally:
|
||||
# Quiesce named source/readback creators before the outer storage
|
||||
# context can remove their directory. Never kill the independent
|
||||
# cleanup observer; it owns the inner storage recovery duty.
|
||||
for name in ('spawner', 'bwrap', 'inner'):
|
||||
descriptor = descriptors.get(name)
|
||||
if descriptor is None:
|
||||
continue
|
||||
dead, _, _ = select.select([descriptor], [], [], 0)
|
||||
if not dead:
|
||||
try:
|
||||
signal.pidfd_send_signal(descriptor, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
# Retain the quiescence duty until exact death is proven.
|
||||
select.select([descriptor], [], [])
|
||||
if child.poll() is None:
|
||||
child.kill()
|
||||
child.wait(timeout=5)
|
||||
observer = descriptors.get('cleanup_observer')
|
||||
if observer is not None:
|
||||
select.select([observer], [], [], 8)
|
||||
if fixture.exists() or set(Path('/dev/shm').glob('rmgr-secret-scan-*'))-previous:
|
||||
raise p.Refused('outer-storage-cleanup-unverified')
|
||||
receipt.update(status='verified',fixture_removed=True,new_scan_storage_absent=True)
|
||||
except Exception as error:
|
||||
receipt['reason']=error.args[0] if isinstance(error,p.Refused) else 'lifecycle-probe-unavailable'
|
||||
finally:
|
||||
if child is not None and child.poll() is None:
|
||||
try:
|
||||
signal.pidfd_send_signal(descriptors['spawner'],signal.SIGKILL)
|
||||
child.wait(timeout=5)
|
||||
except (OSError,subprocess.SubprocessError):
|
||||
pass
|
||||
for fd in descriptors.values():
|
||||
os.close(fd)
|
||||
receipt['fixture_removed']=fixture is None or not fixture.exists()
|
||||
receipt['inner_storage_removed']=inner_area is None or not inner_area.exists()
|
||||
receipt['driver_sha256']=p.sha(Path(__file__))
|
||||
(HERE/'namespace-lifecycle-results.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print(json.dumps({'status':receipt['status'],'fixture_removed':receipt['fixture_removed'],
|
||||
'inner_storage_removed':receipt['inner_storage_removed']}))
|
||||
return 0 if receipt['status']=='verified' else 2
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
parser=argparse.ArgumentParser();parser.add_argument('--spawn',type=Path);parser.add_argument('--inner',type=Path)
|
||||
args=parser.parse_args()
|
||||
raise SystemExit(spawn(args.spawn) if args.spawn else inner(args.inner) if args.inner else main())
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
....... [100%]
|
||||
7 passed in 0.04s
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"status": "unverified", "fixture_removed": true, "inner_storage_removed": true}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"state": "R2 prepared for root review; R1 runtime failed and retained, no acceptance",
|
||||
"owner_head": "b02e1cefb0e07fe246dc9fe9c5a989fe06116587",
|
||||
"source_sha256": "e709c658f779534fcab093207b0a78e49d54fc584c515fdf7ac535ce75f6c400",
|
||||
"scanner_sha256": "88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509",
|
||||
"hook_sha256": "d50cf763dc9814383c6886e30b6650bbbdcb49ddd78889082e3fda3626a2cbfd",
|
||||
"bwrap_sha256": "e318903862396f96de3df57264e0158682b952fd3fb53ac23d876413e7b30f71",
|
||||
"files": {
|
||||
"probe_namespace_hooks.py": "0e680c9ae85242bb510cbe871e14c498e01643eccc3656533e6fb2902030c2d8",
|
||||
"probe_namespace_lifecycle.py": "9021bd7a6a52ebe4ecd52587094b793a967f2578e24c6a30ed7b81c4a9154f52",
|
||||
"test_namespace_profile.py": "04e508a072941c5fa1abe9c1ffbfe4452c1fcabfe4e39c6c0b768d76da6ce689",
|
||||
"run_reviewed_fixture.sh": "07d0b518a4e99c6d88c0cbc2a03c76b20429f0ac7d86d3985ca46528cbffe596"
|
||||
},
|
||||
"expected_hook_cases": 6,
|
||||
"unit_profile_checks_passed": 8,
|
||||
"namespace_policy": {
|
||||
"changed": [
|
||||
"user",
|
||||
"mount",
|
||||
"network"
|
||||
],
|
||||
"preserved": [
|
||||
"pid",
|
||||
"root dev/inode",
|
||||
"dev/shm dev/inode",
|
||||
"uid/gid"
|
||||
],
|
||||
"writable_bind_only": "/dev/shm to existing /dev/shm",
|
||||
"scanner_shadow": "new generated empty0400file readonlybound over exact admitted scanner path only for unavailable cases"
|
||||
},
|
||||
"expected_unavailable_result": {
|
||||
"status": "unverified",
|
||||
"exit_code": 2,
|
||||
"reason": "scanner-file-refused",
|
||||
"scanned_blobs": 0
|
||||
},
|
||||
"forbidden_actions": [
|
||||
"host scanner chmod/bytes changes",
|
||||
"hook/source/config changes",
|
||||
"real index open/write",
|
||||
"real source blob scan",
|
||||
"network/provider calls",
|
||||
"new install",
|
||||
"production configuration",
|
||||
"external messages"
|
||||
],
|
||||
"cleanup": "reviewed canonical tmpfs+pidfd watchdog; bwrapdie-with-parent; source childrenPDEATHSIG; actual namespace hard-loss fixture first, exact disposable spawner only, independent cleanupobserver survives",
|
||||
"bounds": {
|
||||
"git_seconds": 20,
|
||||
"hook_case_seconds": 30,
|
||||
"child_cpu_seconds": 20,
|
||||
"child_virtual_address_GiB": 8,
|
||||
"generated_git_file_limit_MiB": 16,
|
||||
"bwrap_hook_file_limit_MiB": 64,
|
||||
"outer_watchdog_seconds": 180,
|
||||
"watchdog_TERM_grace_seconds": 5,
|
||||
"uncertain_kernel_quiescence": "retain observer duty until exact pidfddeath; never delete under known live creator"
|
||||
},
|
||||
"canonical_mutations_performed": false,
|
||||
"runtime_fixture_executed": false,
|
||||
"registration_gate": "atomic inner PID+namespace early-ready; main opens exact bwrap+inner pidfds and verifies live before writing exact generated gate; inner refuses storage after8seconds without release",
|
||||
"superseded_lifecycle_sha256": "7b74d96c867d44a0e994890d58d0d039028684ded3611ffb65f9c7b09d6f5527",
|
||||
"failed_r1": {
|
||||
"driver_sha256": "c46efb37e02349ba73f83b46c8fd8b1abc286282be8dc0a105ae76911e1ca266",
|
||||
"result": "namespace-lifecycle-preflight-failed",
|
||||
"fixture_removed": true,
|
||||
"inner_storage_removed": true,
|
||||
"raw_packet": "failed-r1/",
|
||||
"stderr_history": "R1 discarded spawner/bwrap stderr; cannot recover retroactively"
|
||||
},
|
||||
"r2_delta": [
|
||||
"atomic release-gate publication",
|
||||
"fixed public phase readback with exceptionclass/errno/whitelisted source reason",
|
||||
"lifecycle-only bwrap/spawner stderr capture capped8192bytes each; file growth capped1MiB per child",
|
||||
"no canonical or actual-hook driver changes"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"status": "unverified",
|
||||
"scope": "generated namespace hard-parent-loss process tree only",
|
||||
"real_index_opened": false,
|
||||
"real_credentials_read": false,
|
||||
"provider_calls": 0,
|
||||
"host_scanner_modified": false,
|
||||
"last_inner_phase": {
|
||||
"phase": "inner-exception",
|
||||
"exception_type": "PermissionError",
|
||||
"errno": 13
|
||||
},
|
||||
"disposable_spawner_returncode": 26,
|
||||
"bwrap_stderr_public": "",
|
||||
"bwrap_stderr_truncated": false,
|
||||
"spawner_stderr_public": "",
|
||||
"spawner_stderr_truncated": false,
|
||||
"reason": "namespace-lifecycle-preflight-failed",
|
||||
"fixture_removed": true,
|
||||
"inner_storage_removed": true,
|
||||
"driver_sha256": "9021bd7a6a52ebe4ecd52587094b793a967f2578e24c6a30ed7b81c4a9154f52"
|
||||
}
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generated alternate-index hook probes; no host scanner/hook/source mutation.
|
||||
|
||||
Only persistent writes are fixed-metadata receipts in this review packet.
|
||||
User+mount namespaces shadow the exact scanner path; PID namespace stays shared
|
||||
so the admitted canonical scanner's independent cleanup watchdog can survive.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import types
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
BASE = HERE.parent.parent / 'netkingdom-target-2026-10-16/scanner-batch-r1/probe_configured_hooks.py'
|
||||
BASE_SHA = 'c91d2dc83f04ec17147c0e1410e42f4817b69e4cc5a02201682e5fa7b021b6f4'
|
||||
BWRAP = Path('/usr/bin/bwrap')
|
||||
BWRAP_SHA = 'e318903862396f96de3df57264e0158682b952fd3fb53ac23d876413e7b30f71'
|
||||
OWNER_HEAD = b'b02e1cefb0e07fe246dc9fe9c5a989fe06116587'
|
||||
SOURCE = Path('/home/worsch/repo-manager/src/repo_manager/secret_scan.py')
|
||||
SOURCE_SHA = 'e709c658f779534fcab093207b0a78e49d54fc584c515fdf7ac535ce75f6c400'
|
||||
BINARY = Path('/home/worsch/.local/lib/repo-manager/gitleaks/8.30.1/gitleaks')
|
||||
BINARY_SHA = '88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509'
|
||||
HOOK_SHA = 'd50cf763dc9814383c6886e30b6650bbbdcb49ddd78889082e3fda3626a2cbfd'
|
||||
|
||||
|
||||
class Refused(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def identity(path):
|
||||
item = path.stat()
|
||||
return [item.st_dev, item.st_ino]
|
||||
|
||||
|
||||
def namespaces():
|
||||
return {key: os.readlink('/proc/self/ns/' + key) for key in ('user', 'mnt', 'pid', 'net')}
|
||||
|
||||
|
||||
def load_bound(path, digest, name):
|
||||
content = path.read_bytes()
|
||||
if hashlib.sha256(content).hexdigest() != digest:
|
||||
raise Refused('bound-source-drift')
|
||||
module = types.ModuleType(name)
|
||||
module.__file__ = str(path)
|
||||
exec(compile(content, str(path), 'exec'), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def command(root, env, spec_path, shadow=None):
|
||||
argv = [str(BWRAP), '--unshare-user', '--unshare-net', '--die-with-parent', '--clearenv',
|
||||
'--ro-bind', '/', '/', '--bind', '/dev/shm', '/dev/shm',
|
||||
'--uid', str(os.getuid()), '--gid', str(os.getgid()), '--chdir', str(root)]
|
||||
if shadow is not None:
|
||||
argv += ['--ro-bind', str(shadow), str(BINARY)]
|
||||
for key, value in sorted(env.items()):
|
||||
argv += ['--setenv', key, value]
|
||||
argv += ['--', '/usr/bin/python3', '-I', str(Path(__file__).resolve()), '--inside', str(spec_path)]
|
||||
return argv
|
||||
|
||||
|
||||
def check_spec(spec):
|
||||
if set(spec) != {'root', 'hook', 'shadow', 'readback', 'host_root', 'host_shm',
|
||||
'host_ns', 'uid', 'gid', 'interpreter_sha256', 'driver_sha256'}:
|
||||
raise Refused('spec-schema-refused')
|
||||
allowed = {
|
||||
'/home/worsch/repo-manager': '/home/worsch/repo-manager/.githooks/pre-commit',
|
||||
'/home/worsch/the-custodian': '/home/worsch/the-custodian/.git/hooks/pre-commit',
|
||||
}
|
||||
if allowed.get(spec['root']) != spec['hook'] or not isinstance(spec['shadow'], bool):
|
||||
raise Refused('spec-target-refused')
|
||||
destination = Path(spec['readback'])
|
||||
if not str(destination).startswith('/dev/shm/rmgr-secret-scan-'):
|
||||
raise Refused('readback-storage-refused')
|
||||
|
||||
|
||||
def inside(spec_path):
|
||||
# All targets originate in the fixed reviewed outer driver. Refuse changes
|
||||
# before executing a hook; only fixed metadata is written to volatile storage.
|
||||
try:
|
||||
spec = json.loads(spec_path.read_text())
|
||||
check_spec(spec)
|
||||
observed_ns = namespaces()
|
||||
if (identity(Path('/')) != spec['host_root']
|
||||
or identity(Path('/dev/shm')) != spec['host_shm']
|
||||
or os.getuid() != spec['uid'] or os.getgid() != spec['gid']
|
||||
or observed_ns['user'] == spec['host_ns']['user']
|
||||
or observed_ns['mnt'] == spec['host_ns']['mnt']
|
||||
or observed_ns['net'] == spec['host_ns']['net']
|
||||
or observed_ns['pid'] != spec['host_ns']['pid']
|
||||
or not (os.statvfs('/').f_flag & os.ST_RDONLY)
|
||||
or os.statvfs('/dev/shm').f_flag & os.ST_RDONLY):
|
||||
raise Refused('namespace-identity-refused')
|
||||
if (sha(SOURCE) != SOURCE_SHA or sha(Path(spec['hook'])) != HOOK_SHA
|
||||
or sha(Path(__file__).resolve()) != spec['driver_sha256']
|
||||
or shutil.which('python3') != '/usr/bin/python3'
|
||||
or sha(Path('/usr/bin/python3')) != spec['interpreter_sha256']):
|
||||
raise Refused('namespace-code-readback-refused')
|
||||
scanner = BINARY.stat()
|
||||
if spec['shadow']:
|
||||
if (not stat.S_ISREG(scanner.st_mode) or scanner.st_size != 0
|
||||
or stat.S_IMODE(scanner.st_mode) != 0o400
|
||||
or sha(BINARY) != hashlib.sha256(b'').hexdigest()):
|
||||
raise Refused('scanner-shadow-readback-refused')
|
||||
elif sha(BINARY) != BINARY_SHA or stat.S_IMODE(scanner.st_mode) != 0o755:
|
||||
raise Refused('baseline-scanner-readback-refused')
|
||||
record = {'verified': True, 'root_identity_preserved': True,
|
||||
'shm_identity_preserved': True, 'root_readonly': True, 'shm_writable': True,
|
||||
'user_mount_network_namespaces_changed': True, 'pid_namespace_preserved': True,
|
||||
'uid_gid_preserved': True, 'source_sha256': SOURCE_SHA,
|
||||
'hook_sha256': HOOK_SHA, 'interpreter_sha256': spec['interpreter_sha256'],
|
||||
'scanner_view': 'empty-0400-shadow' if spec['shadow'] else 'pinned-real-scanner'}
|
||||
target = Path(spec['readback'])
|
||||
with target.open('x') as output:
|
||||
json.dump(record, output)
|
||||
os.execve(spec['hook'], [spec['hook']], dict(os.environ))
|
||||
except Exception:
|
||||
return 23
|
||||
|
||||
|
||||
def environment(base):
|
||||
env = base.fixed_env()
|
||||
env['GIT_CONFIG_GLOBAL'] = '/dev/null'
|
||||
overrides = [('core.fsmonitor', 'false'), ('core.splitIndex', 'false'),
|
||||
('core.sparseCheckout', 'false'), ('core.untrackedCache', 'false')]
|
||||
env['GIT_CONFIG_COUNT'] = str(len(overrides))
|
||||
for number, (key, value) in enumerate(overrides):
|
||||
env[f'GIT_CONFIG_KEY_{number}'] = key
|
||||
env[f'GIT_CONFIG_VALUE_{number}'] = value
|
||||
return env
|
||||
|
||||
|
||||
def main():
|
||||
receipt = {'status': 'unverified', 'scope': 'two actual configured hooks; generated alternate-index/object fixtures in mount/user/network namespaces',
|
||||
'provider_calls': 0, 'real_credentials_read': False, 'real_index_opened': False,
|
||||
'source_sha256': SOURCE_SHA, 'scanner_sha256': BINARY_SHA, 'hook_sha256': HOOK_SHA,
|
||||
'bwrap_sha256': BWRAP_SHA, 'host_scanner_modified': False, 'rows': []}
|
||||
fixture = None
|
||||
try:
|
||||
if sha(BWRAP) != BWRAP_SHA or BWRAP.stat().st_mode & 0o6022:
|
||||
raise Refused('namespace-binary-refused')
|
||||
base = load_bound(BASE, BASE_SHA, 'reviewed_hook_probe')
|
||||
records = base.precheck()
|
||||
if records[0]['head'] != OWNER_HEAD:
|
||||
raise Refused('owner-head-drift')
|
||||
before_binary = base.info(BINARY)
|
||||
adapter = load_bound(SOURCE, SOURCE_SHA, 'canonical_scanner_storage')
|
||||
prior_storage = set(Path('/dev/shm').glob('rmgr-secret-scan-*'))
|
||||
host_ns = namespaces()
|
||||
host_root = identity(Path('/')); host_shm = identity(Path('/dev/shm'))
|
||||
interpreter_sha = sha(Path('/usr/bin/python3'))
|
||||
with adapter.private_storage() as (fixture, watcher):
|
||||
env = environment(base)
|
||||
key_path = fixture / 'generated-key'
|
||||
base.run(['/usr/bin/ssh-keygen', '-q', '-t', 'ed25519', '-N', '', '-C',
|
||||
'public-generated-namespace-probe', '-f', str(key_path)], fixture, env,
|
||||
adapter=adapter)
|
||||
key = key_path.read_bytes()
|
||||
shadow = fixture / 'unavailable-scanner'
|
||||
with shadow.open('x'):
|
||||
pass
|
||||
shadow.chmod(0o400) # Only this newly created empty disposable file.
|
||||
for number, record in enumerate(records):
|
||||
root = record['root']; hook = record['hook']
|
||||
local = fixture / str(number); local.mkdir(mode=0o700)
|
||||
objects = local / 'objects'; objects.mkdir(mode=0o700)
|
||||
probe_env = dict(env, GIT_INDEX_FILE=str(local/'index'),
|
||||
GIT_OBJECT_DIRECTORY=str(objects), GIT_ALTERNATE_OBJECT_DIRECTORIES=record['objects'])
|
||||
name = '__rmgr_namespace_generated_fixture__.txt'
|
||||
if base.git(root, probe_env, 'ls-tree', '--name-only', 'HEAD', '--', name, adapter=adapter):
|
||||
raise Refused('fixture-name-collision')
|
||||
for case, payload, status, code, unavailable in [
|
||||
('clean-control', b'public namespace hook fixture\n', 'clean', 0, False),
|
||||
('generated-key-control', key, 'blocked', 1, False),
|
||||
('scanner-unavailable', b'public namespace unavailable fixture\n', 'unverified', 2, True),
|
||||
]:
|
||||
if watcher.poll() is not None:
|
||||
raise Refused('outer-watchdog-lost')
|
||||
base.git(root, probe_env, 'read-tree', 'HEAD', adapter=adapter)
|
||||
oid = base.git(root, probe_env, 'hash-object', '-w', '--stdin', data=payload,
|
||||
adapter=adapter).decode().strip()
|
||||
base.git(root, probe_env, 'update-index', '--add', '--cacheinfo',
|
||||
'100644,'+oid+','+name, adapter=adapter)
|
||||
changed = base.git(root, probe_env, 'diff', '--cached', '--name-only', '-z',
|
||||
'--no-renames', '--no-ext-diff', '--no-textconv', adapter=adapter)
|
||||
if changed != name.encode()+b'\0':
|
||||
raise Refused('changed-fixture-scope-refused')
|
||||
spec_path = local / (case+'.spec.json')
|
||||
readback_path = local / (case+'.readback.json')
|
||||
spec = {'root': str(root), 'hook': str(hook), 'shadow': unavailable,
|
||||
'readback': str(readback_path), 'host_root': host_root, 'host_shm': host_shm,
|
||||
'host_ns': host_ns, 'uid': os.getuid(), 'gid': os.getgid(),
|
||||
'interpreter_sha256': interpreter_sha, 'driver_sha256': sha(Path(__file__))}
|
||||
spec_path.write_text(json.dumps(spec))
|
||||
result = subprocess.run(command(root, probe_env, spec_path,
|
||||
shadow if unavailable else None), cwd=root, env=base.fixed_env(),
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30, check=False,
|
||||
preexec_fn=functools.partial(adapter.child_limits, 64*1024**2, os.getpid()))
|
||||
if result.stderr or key.strip() in result.stdout or not readback_path.exists():
|
||||
raise Refused('namespace-or-hook-output-refused')
|
||||
readback = json.loads(readback_path.read_text())
|
||||
body = json.loads(result.stdout)
|
||||
if result.returncode != code or body.get('status') != status:
|
||||
raise Refused('hook-result-refused')
|
||||
if unavailable:
|
||||
if body != {'status': 'unverified', 'scope': 'changed-staged-blobs',
|
||||
'reason': 'scanner-file-refused', 'scanned_blobs': 0}:
|
||||
raise Refused('unavailable-summary-refused')
|
||||
elif body != {'status': status, 'scope': 'changed-staged-blobs',
|
||||
'scanner_version': '8.30.1', 'scanner_sha256': BINARY_SHA,
|
||||
'scanned_blobs': 1, 'blocked_blob_ids': [oid] if status=='blocked' else []}:
|
||||
raise Refused('control-summary-refused')
|
||||
receipt['rows'].append({'repo': root.name, 'case': case, 'status': status,
|
||||
'exit_code': code, 'scanned_blobs': body['scanned_blobs'],
|
||||
'reason': body.get('reason'), 'namespace_readback': readback})
|
||||
if fixture.exists() or set(Path('/dev/shm').glob('rmgr-secret-scan-*')) - prior_storage:
|
||||
raise Refused('volatile-cleanup-unverified')
|
||||
after_records = base.precheck()
|
||||
if (before_binary != base.info(BINARY)
|
||||
or any(a['index_stat'] != b['index_stat'] or a['head'] != b['head']
|
||||
for a, b in zip(records, after_records))):
|
||||
raise Refused('host-state-changed')
|
||||
receipt.update(status='verified', fixture_removed=True, new_scan_storage_absent=True,
|
||||
real_index_stat_unchanged=True, real_heads_unchanged=True,
|
||||
host_scanner_stat_hash_mode_unchanged=True, hook_source_routing_unchanged=True)
|
||||
except Exception as error:
|
||||
receipt['reason'] = error.args[0] if isinstance(error, Refused) else 'namespace-probe-unavailable'
|
||||
receipt['fixture_removed'] = fixture is None or not fixture.exists()
|
||||
receipt['driver_sha256'] = sha(Path(__file__))
|
||||
(HERE/'namespace-hook-results.json').write_text(json.dumps(receipt, indent=2)+'\n')
|
||||
print(json.dumps({'status':receipt['status'],'cases':len(receipt['rows']),
|
||||
'fixture_removed':receipt['fixture_removed']}))
|
||||
return 0 if receipt['status']=='verified' else 2
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser=argparse.ArgumentParser()
|
||||
parser.add_argument('--inside', type=Path)
|
||||
args=parser.parse_args()
|
||||
raise SystemExit(inside(args.inside) if args.inside else main())
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Hard-loss fixture targets only a newly spawned generated-data process tree."""
|
||||
import argparse
|
||||
import functools
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import select
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
spec = importlib.util.spec_from_file_location('namespace_profile', HERE/'probe_namespace_hooks.py')
|
||||
p = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(p)
|
||||
|
||||
|
||||
def adapter():
|
||||
return p.load_bound(p.SOURCE, p.SOURCE_SHA, 'reviewed_lifecycle_storage')
|
||||
|
||||
|
||||
def phase(data, name, error=None):
|
||||
if 'phase_readback' not in data:
|
||||
return
|
||||
record = {'phase':name}
|
||||
if error is not None:
|
||||
record['exception_type'] = type(error).__name__
|
||||
record['errno'] = getattr(error, 'errno', None)
|
||||
allowed = {'volatile-storage-unavailable','cleanup-watchdog-unavailable',
|
||||
'volatile-storage-identity-changed','cleanup-watchdog-failed'}
|
||||
if error.args and isinstance(error.args[0],str) and error.args[0] in allowed:
|
||||
record['fixed_source_reason'] = error.args[0]
|
||||
target=Path(data['phase_readback']); temporary=target.with_suffix('.tmp')
|
||||
temporary.write_text(json.dumps(record)); temporary.replace(target)
|
||||
|
||||
|
||||
def inner(path):
|
||||
data=json.loads(path.read_text())
|
||||
try:
|
||||
phase(data, 'inner-started')
|
||||
return inner_body(data)
|
||||
except Exception as error:
|
||||
phase(data, 'inner-exception', error)
|
||||
return 26
|
||||
|
||||
|
||||
def inner_body(data):
|
||||
current = p.namespaces()
|
||||
if (p.identity(Path('/')) != data['root'] or p.identity(Path('/dev/shm')) != data['shm']
|
||||
or current['pid'] != data['ns']['pid'] or current['mnt'] == data['ns']['mnt']
|
||||
or current['user'] == data['ns']['user'] or current['net'] == data['ns']['net']
|
||||
or p.sha(Path('/usr/bin/python3')) != data['interpreter_sha256']):
|
||||
return 23
|
||||
phase(data, 'namespace-verified')
|
||||
# Register the exact inner process before any source-bearing storage can
|
||||
# exist. Publication is atomic; the only earlier effect is PID metadata.
|
||||
early = Path(data['early_ready'])
|
||||
temporary = early.with_suffix('.tmp')
|
||||
temporary.write_text(json.dumps({'inner_pid':os.getpid(), 'namespace_identity_verified':True}))
|
||||
temporary.replace(early)
|
||||
until = time.monotonic() + 8
|
||||
gate = Path(data['release_gate'])
|
||||
while not gate.exists() and time.monotonic() < until:
|
||||
time.sleep(.01)
|
||||
if not gate.exists() or gate.read_text() != data['gate_token']:
|
||||
phase(data, 'gate-token-refused')
|
||||
return 25
|
||||
phase(data, 'gate-released')
|
||||
a = adapter()
|
||||
phase(data, 'adapter-loaded')
|
||||
with a.private_storage() as (area, watcher):
|
||||
phase(data, 'storage-entered')
|
||||
(area/'public-generated-sentinel').write_bytes(b'public generated parent-loss sentinel')
|
||||
phase(data, 'sentinel-written')
|
||||
readback = {'storage':str(area), 'storage_identity':p.identity(area),
|
||||
'inner_pid':os.getpid(), 'watcher_pid':watcher.pid,
|
||||
'namespace_identity_verified':True}
|
||||
temporary = Path(data['readback'] + '.tmp')
|
||||
temporary.write_text(json.dumps(readback))
|
||||
temporary.replace(Path(data['readback']))
|
||||
phase(data, 'full-readback-published')
|
||||
time.sleep(30)
|
||||
return 24 # The observer must kill the disposable spawner before normal expiry.
|
||||
|
||||
|
||||
def spawn(path):
|
||||
a = adapter()
|
||||
data = json.loads(path.read_text())
|
||||
env = {'PATH':'/usr/bin:/bin','HOME':'/home/worsch','LANG':'C'}
|
||||
args = p.command(Path('/home/worsch/repo-manager'), env, path)
|
||||
args = args[:args.index('--')+1] + ['/usr/bin/python3','-I',str(Path(__file__).resolve()),'--inner',str(path)]
|
||||
diagnostics = open(data['bwrap_stderr'], 'wb')
|
||||
child = subprocess.Popen(args, env=env, stdout=subprocess.DEVNULL, stderr=diagnostics,
|
||||
preexec_fn=functools.partial(a.child_limits,1024**2,os.getpid()))
|
||||
try:
|
||||
temporary = Path(data['bwrap_pid'] + '.tmp')
|
||||
temporary.write_text(str(child.pid))
|
||||
temporary.replace(Path(data['bwrap_pid']))
|
||||
return child.wait(timeout=30)
|
||||
finally:
|
||||
if child.poll() is None:
|
||||
child.kill(); child.wait(timeout=5)
|
||||
diagnostics.close()
|
||||
|
||||
|
||||
def main():
|
||||
receipt={'status':'unverified','scope':'generated namespace hard-parent-loss process tree only',
|
||||
'real_index_opened':False,'real_credentials_read':False,'provider_calls':0,
|
||||
'host_scanner_modified':False}
|
||||
descriptors={}; child=None; fixture=None; inner_area=None
|
||||
try:
|
||||
if p.sha(p.BWRAP) != p.BWRAP_SHA:
|
||||
raise p.Refused('bwrap-drift')
|
||||
a=adapter()
|
||||
previous=set(Path('/dev/shm').glob('rmgr-secret-scan-*'))
|
||||
with a.private_storage() as (fixture, outer_watcher):
|
||||
data={'root':p.identity(Path('/')),'shm':p.identity(Path('/dev/shm')),
|
||||
'ns':p.namespaces(),'readback':str(fixture/'inner.readback'),
|
||||
'bwrap_pid':str(fixture/'bwrap.pid'),
|
||||
'early_ready':str(fixture/'inner.early.ready'),
|
||||
'phase_readback':str(fixture/'inner.phase.json'),
|
||||
'bwrap_stderr':str(fixture/'bwrap.stderr'),
|
||||
'release_gate':str(fixture/'release.gate'), 'gate_token':os.urandom(16).hex(),
|
||||
'interpreter_sha256':p.sha(Path('/usr/bin/python3'))}
|
||||
path=fixture/'spec.json'; path.write_text(json.dumps(data))
|
||||
diagnostics = open(fixture/'spawner.stderr', 'wb')
|
||||
child=subprocess.Popen([sys.executable,'-I',str(Path(__file__).resolve()),'--spawn',str(path)],
|
||||
env={'PATH':'/usr/bin:/bin','HOME':'/home/worsch','LANG':'C'},
|
||||
stdout=subprocess.DEVNULL,stderr=diagnostics,
|
||||
preexec_fn=functools.partial(a.child_limits,1024**2,os.getpid()))
|
||||
try:
|
||||
descriptors['spawner']=os.pidfd_open(child.pid)
|
||||
# Wait for atomic pre-effect readiness, register both exact
|
||||
# process identities, then release only this generated gate.
|
||||
until=time.monotonic()+8
|
||||
early=Path(data['early_ready']); bwrap_pid=Path(data['bwrap_pid'])
|
||||
while (not early.exists() or not bwrap_pid.exists()) and child.poll() is None and time.monotonic()<until:
|
||||
time.sleep(.01)
|
||||
if not early.exists() or not bwrap_pid.exists():
|
||||
raise p.Refused('namespace-early-registration-failed')
|
||||
ready=json.loads(early.read_text())
|
||||
if set(ready) != {'inner_pid','namespace_identity_verified'} or not ready['namespace_identity_verified']:
|
||||
raise p.Refused('namespace-early-readiness-refused')
|
||||
descriptors['bwrap']=os.pidfd_open(int(bwrap_pid.read_text()))
|
||||
descriptors['inner']=os.pidfd_open(ready['inner_pid'])
|
||||
for descriptor in (descriptors['bwrap'],descriptors['inner']):
|
||||
dead,_,_=select.select([descriptor],[],[],0)
|
||||
if dead:
|
||||
raise p.Refused('namespace-creator-died-before-release')
|
||||
gate = Path(data['release_gate'])
|
||||
temporary_gate = gate.with_suffix('.tmp')
|
||||
temporary_gate.write_text(data['gate_token'])
|
||||
temporary_gate.replace(gate)
|
||||
until=time.monotonic()+8
|
||||
readback=Path(data['readback'])
|
||||
while not readback.exists() and child.poll() is None and time.monotonic()<until:
|
||||
time.sleep(.02)
|
||||
if not readback.exists():
|
||||
raise p.Refused('namespace-lifecycle-preflight-failed')
|
||||
body=json.loads(readback.read_text()); inner_area=Path(body['storage'])
|
||||
if (not body['namespace_identity_verified'] or p.identity(inner_area)!=body['storage_identity']
|
||||
or not str(inner_area).startswith('/dev/shm/rmgr-secret-scan-')):
|
||||
raise p.Refused('lifecycle-readback-refused')
|
||||
if body['inner_pid'] != ready['inner_pid']:
|
||||
raise p.Refused('namespace-inner-identity-changed')
|
||||
descriptors['cleanup_observer']=os.pidfd_open(body['watcher_pid'])
|
||||
# Exactly this new disposable spawner is killed. The calling tool,
|
||||
# shell, test harness, observer and their process groups are untouched.
|
||||
signal.pidfd_send_signal(descriptors['spawner'],signal.SIGKILL)
|
||||
child.wait(timeout=5)
|
||||
until=time.monotonic()+8
|
||||
pending=set(descriptors.values())
|
||||
while pending and time.monotonic()<until:
|
||||
ready,_,_=select.select(list(pending),[],[],max(0,until-time.monotonic()))
|
||||
pending.difference_update(ready)
|
||||
if pending or inner_area.exists():
|
||||
raise p.Refused('namespace-hard-loss-cleanup-unverified')
|
||||
receipt.update(namespace_identity_verified=True, pre_effect_pidfd_registration_verified=True,
|
||||
exact_disposable_parent_killed=True,
|
||||
bwrap_inner_and_cleanup_observer_death_verified=True,inner_storage_removed=True)
|
||||
finally:
|
||||
# Quiesce named source/readback creators before the outer storage
|
||||
# context can remove their directory. Never kill the independent
|
||||
# cleanup observer; it owns the inner storage recovery duty.
|
||||
for name in ('spawner', 'bwrap', 'inner'):
|
||||
descriptor = descriptors.get(name)
|
||||
if descriptor is None:
|
||||
continue
|
||||
dead, _, _ = select.select([descriptor], [], [], 0)
|
||||
if not dead:
|
||||
try:
|
||||
signal.pidfd_send_signal(descriptor, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
# Retain the quiescence duty until exact death is proven.
|
||||
select.select([descriptor], [], [])
|
||||
if child.poll() is None:
|
||||
child.kill()
|
||||
child.wait(timeout=5)
|
||||
observer = descriptors.get('cleanup_observer')
|
||||
if observer is not None:
|
||||
select.select([observer], [], [], 8)
|
||||
diagnostics.close()
|
||||
phase_path=Path(data['phase_readback'])
|
||||
if phase_path.exists():
|
||||
receipt['last_inner_phase']=json.loads(phase_path.read_text())
|
||||
receipt['disposable_spawner_returncode']=child.returncode
|
||||
# This lifecycle fixture handles public sentinel bytes only, no
|
||||
# keys/indexes/source blobs. Retain bounded actual diagnostics.
|
||||
for name in ('bwrap','spawner'):
|
||||
diagnostic=fixture/(name+'.stderr')
|
||||
if diagnostic.exists():
|
||||
with diagnostic.open('rb') as output:
|
||||
content=output.read(8193)
|
||||
receipt[name+'_stderr_public']=content[:8192].decode('utf-8','replace')
|
||||
receipt[name+'_stderr_truncated']=len(content)>8192
|
||||
if fixture.exists() or set(Path('/dev/shm').glob('rmgr-secret-scan-*'))-previous:
|
||||
raise p.Refused('outer-storage-cleanup-unverified')
|
||||
receipt.update(status='verified',fixture_removed=True,new_scan_storage_absent=True)
|
||||
except Exception as error:
|
||||
receipt['reason']=error.args[0] if isinstance(error,p.Refused) else 'lifecycle-probe-unavailable'
|
||||
finally:
|
||||
if child is not None and child.poll() is None:
|
||||
try:
|
||||
signal.pidfd_send_signal(descriptors['spawner'],signal.SIGKILL)
|
||||
child.wait(timeout=5)
|
||||
except (OSError,subprocess.SubprocessError):
|
||||
pass
|
||||
for fd in descriptors.values():
|
||||
os.close(fd)
|
||||
receipt['fixture_removed']=fixture is None or not fixture.exists()
|
||||
receipt['inner_storage_removed']=inner_area is None or not inner_area.exists()
|
||||
receipt['driver_sha256']=p.sha(Path(__file__))
|
||||
(HERE/'namespace-lifecycle-results.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print(json.dumps({'status':receipt['status'],'fixture_removed':receipt['fixture_removed'],
|
||||
'inner_storage_removed':receipt['inner_storage_removed']}))
|
||||
return 0 if receipt['status']=='verified' else 2
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
parser=argparse.ArgumentParser();parser.add_argument('--spawn',type=Path);parser.add_argument('--inner',type=Path)
|
||||
args=parser.parse_args()
|
||||
raise SystemExit(spawn(args.spawn) if args.spawn else inner(args.inner) if args.inner else main())
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
....... [100%]
|
||||
7 passed in 0.04s
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"status": "verified", "cases": 6, "fixture_removed": true}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"status": "verified", "fixture_removed": true, "inner_storage_removed": true}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
--- a/probe_namespace_lifecycle.py
|
||||
+++ b/probe_namespace_lifecycle.py
|
||||
@@ -30,6 +30,18 @@
|
||||
or current['user'] == data['ns']['user'] or current['net'] == data['ns']['net']
|
||||
or p.sha(Path('/usr/bin/python3')) != data['interpreter_sha256']):
|
||||
return 23
|
||||
+ # Register the exact inner process before any source-bearing storage can
|
||||
+ # exist. Publication is atomic; the only earlier effect is PID metadata.
|
||||
+ early = Path(data['early_ready'])
|
||||
+ temporary = early.with_suffix('.tmp')
|
||||
+ temporary.write_text(json.dumps({'inner_pid':os.getpid(), 'namespace_identity_verified':True}))
|
||||
+ temporary.replace(early)
|
||||
+ until = time.monotonic() + 8
|
||||
+ gate = Path(data['release_gate'])
|
||||
+ while not gate.exists() and time.monotonic() < until:
|
||||
+ time.sleep(.01)
|
||||
+ if not gate.exists() or gate.read_text() != data['gate_token']:
|
||||
+ return 25
|
||||
a = adapter()
|
||||
with a.private_storage() as (area, watcher):
|
||||
(area/'public-generated-sentinel').write_bytes(b'public generated parent-loss sentinel')
|
||||
@@ -52,7 +64,9 @@
|
||||
child = subprocess.Popen(args, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
preexec_fn=functools.partial(a.child_limits,64*1024**2,os.getpid()))
|
||||
try:
|
||||
- Path(data['bwrap_pid']).write_text(str(child.pid))
|
||||
+ temporary = Path(data['bwrap_pid'] + '.tmp')
|
||||
+ temporary.write_text(str(child.pid))
|
||||
+ temporary.replace(Path(data['bwrap_pid']))
|
||||
return child.wait(timeout=30)
|
||||
finally:
|
||||
if child.poll() is None:
|
||||
@@ -73,6 +87,8 @@
|
||||
data={'root':p.identity(Path('/')),'shm':p.identity(Path('/dev/shm')),
|
||||
'ns':p.namespaces(),'readback':str(fixture/'inner.readback'),
|
||||
'bwrap_pid':str(fixture/'bwrap.pid'),
|
||||
+ 'early_ready':str(fixture/'inner.early.ready'),
|
||||
+ 'release_gate':str(fixture/'release.gate'), 'gate_token':os.urandom(16).hex(),
|
||||
'interpreter_sha256':p.sha(Path('/usr/bin/python3'))}
|
||||
path=fixture/'spec.json'; path.write_text(json.dumps(data))
|
||||
child=subprocess.Popen([sys.executable,'-I',str(Path(__file__).resolve()),'--spawn',str(path)],
|
||||
@@ -81,6 +97,25 @@
|
||||
preexec_fn=functools.partial(a.child_limits,64*1024**2,os.getpid()))
|
||||
try:
|
||||
descriptors['spawner']=os.pidfd_open(child.pid)
|
||||
+ # Wait for atomic pre-effect readiness, register both exact
|
||||
+ # process identities, then release only this generated gate.
|
||||
+ until=time.monotonic()+8
|
||||
+ early=Path(data['early_ready']); bwrap_pid=Path(data['bwrap_pid'])
|
||||
+ while (not early.exists() or not bwrap_pid.exists()) and child.poll() is None and time.monotonic()<until:
|
||||
+ time.sleep(.01)
|
||||
+ if not early.exists() or not bwrap_pid.exists():
|
||||
+ raise p.Refused('namespace-early-registration-failed')
|
||||
+ ready=json.loads(early.read_text())
|
||||
+ if set(ready) != {'inner_pid','namespace_identity_verified'} or not ready['namespace_identity_verified']:
|
||||
+ raise p.Refused('namespace-early-readiness-refused')
|
||||
+ descriptors['bwrap']=os.pidfd_open(int(bwrap_pid.read_text()))
|
||||
+ descriptors['inner']=os.pidfd_open(ready['inner_pid'])
|
||||
+ for descriptor in (descriptors['bwrap'],descriptors['inner']):
|
||||
+ dead,_,_=select.select([descriptor],[],[],0)
|
||||
+ if dead:
|
||||
+ raise p.Refused('namespace-creator-died-before-release')
|
||||
+ with Path(data['release_gate']).open('x') as gate:
|
||||
+ gate.write(data['gate_token'])
|
||||
until=time.monotonic()+8
|
||||
readback=Path(data['readback'])
|
||||
while not readback.exists() and child.poll() is None and time.monotonic()<until:
|
||||
@@ -91,8 +126,8 @@
|
||||
if (not body['namespace_identity_verified'] or p.identity(inner_area)!=body['storage_identity']
|
||||
or not str(inner_area).startswith('/dev/shm/rmgr-secret-scan-')):
|
||||
raise p.Refused('lifecycle-readback-refused')
|
||||
- descriptors['bwrap']=os.pidfd_open(int(Path(data['bwrap_pid']).read_text()))
|
||||
- descriptors['inner']=os.pidfd_open(body['inner_pid'])
|
||||
+ if body['inner_pid'] != ready['inner_pid']:
|
||||
+ raise p.Refused('namespace-inner-identity-changed')
|
||||
descriptors['cleanup_observer']=os.pidfd_open(body['watcher_pid'])
|
||||
# Exactly this new disposable spawner is killed. The calling tool,
|
||||
# shell, test harness, observer and their process groups are untouched.
|
||||
@@ -105,7 +140,8 @@
|
||||
pending.difference_update(ready)
|
||||
if pending or inner_area.exists():
|
||||
raise p.Refused('namespace-hard-loss-cleanup-unverified')
|
||||
- receipt.update(namespace_identity_verified=True, exact_disposable_parent_killed=True,
|
||||
+ receipt.update(namespace_identity_verified=True, pre_effect_pidfd_registration_verified=True,
|
||||
+ exact_disposable_parent_killed=True,
|
||||
bwrap_inner_and_cleanup_observer_death_verified=True,inner_storage_removed=True)
|
||||
finally:
|
||||
# Quiesce named source/readback creators before the outer storage
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
--- a/probe_namespace_lifecycle.py
|
||||
+++ b/probe_namespace_lifecycle.py
|
||||
@@ -22,14 +22,39 @@
|
||||
return p.load_bound(p.SOURCE, p.SOURCE_SHA, 'reviewed_lifecycle_storage')
|
||||
|
||||
|
||||
+def phase(data, name, error=None):
|
||||
+ if 'phase_readback' not in data:
|
||||
+ return
|
||||
+ record = {'phase':name}
|
||||
+ if error is not None:
|
||||
+ record['exception_type'] = type(error).__name__
|
||||
+ record['errno'] = getattr(error, 'errno', None)
|
||||
+ allowed = {'volatile-storage-unavailable','cleanup-watchdog-unavailable',
|
||||
+ 'volatile-storage-identity-changed','cleanup-watchdog-failed'}
|
||||
+ if error.args and isinstance(error.args[0],str) and error.args[0] in allowed:
|
||||
+ record['fixed_source_reason'] = error.args[0]
|
||||
+ target=Path(data['phase_readback']); temporary=target.with_suffix('.tmp')
|
||||
+ temporary.write_text(json.dumps(record)); temporary.replace(target)
|
||||
+
|
||||
+
|
||||
def inner(path):
|
||||
- data = json.loads(path.read_text())
|
||||
+ data=json.loads(path.read_text())
|
||||
+ try:
|
||||
+ phase(data, 'inner-started')
|
||||
+ return inner_body(data)
|
||||
+ except Exception as error:
|
||||
+ phase(data, 'inner-exception', error)
|
||||
+ return 26
|
||||
+
|
||||
+
|
||||
+def inner_body(data):
|
||||
current = p.namespaces()
|
||||
if (p.identity(Path('/')) != data['root'] or p.identity(Path('/dev/shm')) != data['shm']
|
||||
or current['pid'] != data['ns']['pid'] or current['mnt'] == data['ns']['mnt']
|
||||
or current['user'] == data['ns']['user'] or current['net'] == data['ns']['net']
|
||||
or p.sha(Path('/usr/bin/python3')) != data['interpreter_sha256']):
|
||||
return 23
|
||||
+ phase(data, 'namespace-verified')
|
||||
# Register the exact inner process before any source-bearing storage can
|
||||
# exist. Publication is atomic; the only earlier effect is PID metadata.
|
||||
early = Path(data['early_ready'])
|
||||
@@ -41,16 +66,22 @@
|
||||
while not gate.exists() and time.monotonic() < until:
|
||||
time.sleep(.01)
|
||||
if not gate.exists() or gate.read_text() != data['gate_token']:
|
||||
+ phase(data, 'gate-token-refused')
|
||||
return 25
|
||||
+ phase(data, 'gate-released')
|
||||
a = adapter()
|
||||
+ phase(data, 'adapter-loaded')
|
||||
with a.private_storage() as (area, watcher):
|
||||
+ phase(data, 'storage-entered')
|
||||
(area/'public-generated-sentinel').write_bytes(b'public generated parent-loss sentinel')
|
||||
+ phase(data, 'sentinel-written')
|
||||
readback = {'storage':str(area), 'storage_identity':p.identity(area),
|
||||
'inner_pid':os.getpid(), 'watcher_pid':watcher.pid,
|
||||
'namespace_identity_verified':True}
|
||||
temporary = Path(data['readback'] + '.tmp')
|
||||
temporary.write_text(json.dumps(readback))
|
||||
temporary.replace(Path(data['readback']))
|
||||
+ phase(data, 'full-readback-published')
|
||||
time.sleep(30)
|
||||
return 24 # The observer must kill the disposable spawner before normal expiry.
|
||||
|
||||
@@ -61,8 +92,9 @@
|
||||
env = {'PATH':'/usr/bin:/bin','HOME':'/home/worsch','LANG':'C'}
|
||||
args = p.command(Path('/home/worsch/repo-manager'), env, path)
|
||||
args = args[:args.index('--')+1] + ['/usr/bin/python3','-I',str(Path(__file__).resolve()),'--inner',str(path)]
|
||||
- child = subprocess.Popen(args, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
- preexec_fn=functools.partial(a.child_limits,64*1024**2,os.getpid()))
|
||||
+ diagnostics = open(data['bwrap_stderr'], 'wb')
|
||||
+ child = subprocess.Popen(args, env=env, stdout=subprocess.DEVNULL, stderr=diagnostics,
|
||||
+ preexec_fn=functools.partial(a.child_limits,1024**2,os.getpid()))
|
||||
try:
|
||||
temporary = Path(data['bwrap_pid'] + '.tmp')
|
||||
temporary.write_text(str(child.pid))
|
||||
@@ -71,6 +103,7 @@
|
||||
finally:
|
||||
if child.poll() is None:
|
||||
child.kill(); child.wait(timeout=5)
|
||||
+ diagnostics.close()
|
||||
|
||||
|
||||
def main():
|
||||
@@ -88,13 +121,16 @@
|
||||
'ns':p.namespaces(),'readback':str(fixture/'inner.readback'),
|
||||
'bwrap_pid':str(fixture/'bwrap.pid'),
|
||||
'early_ready':str(fixture/'inner.early.ready'),
|
||||
+ 'phase_readback':str(fixture/'inner.phase.json'),
|
||||
+ 'bwrap_stderr':str(fixture/'bwrap.stderr'),
|
||||
'release_gate':str(fixture/'release.gate'), 'gate_token':os.urandom(16).hex(),
|
||||
'interpreter_sha256':p.sha(Path('/usr/bin/python3'))}
|
||||
path=fixture/'spec.json'; path.write_text(json.dumps(data))
|
||||
+ diagnostics = open(fixture/'spawner.stderr', 'wb')
|
||||
child=subprocess.Popen([sys.executable,'-I',str(Path(__file__).resolve()),'--spawn',str(path)],
|
||||
env={'PATH':'/usr/bin:/bin','HOME':'/home/worsch','LANG':'C'},
|
||||
- stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,
|
||||
- preexec_fn=functools.partial(a.child_limits,64*1024**2,os.getpid()))
|
||||
+ stdout=subprocess.DEVNULL,stderr=diagnostics,
|
||||
+ preexec_fn=functools.partial(a.child_limits,1024**2,os.getpid()))
|
||||
try:
|
||||
descriptors['spawner']=os.pidfd_open(child.pid)
|
||||
# Wait for atomic pre-effect readiness, register both exact
|
||||
@@ -114,8 +150,10 @@
|
||||
dead,_,_=select.select([descriptor],[],[],0)
|
||||
if dead:
|
||||
raise p.Refused('namespace-creator-died-before-release')
|
||||
- with Path(data['release_gate']).open('x') as gate:
|
||||
- gate.write(data['gate_token'])
|
||||
+ gate = Path(data['release_gate'])
|
||||
+ temporary_gate = gate.with_suffix('.tmp')
|
||||
+ temporary_gate.write_text(data['gate_token'])
|
||||
+ temporary_gate.replace(gate)
|
||||
until=time.monotonic()+8
|
||||
readback=Path(data['readback'])
|
||||
while not readback.exists() and child.poll() is None and time.monotonic()<until:
|
||||
@@ -165,6 +203,20 @@
|
||||
observer = descriptors.get('cleanup_observer')
|
||||
if observer is not None:
|
||||
select.select([observer], [], [], 8)
|
||||
+ diagnostics.close()
|
||||
+ phase_path=Path(data['phase_readback'])
|
||||
+ if phase_path.exists():
|
||||
+ receipt['last_inner_phase']=json.loads(phase_path.read_text())
|
||||
+ receipt['disposable_spawner_returncode']=child.returncode
|
||||
+ # This lifecycle fixture handles public sentinel bytes only, no
|
||||
+ # keys/indexes/source blobs. Retain bounded actual diagnostics.
|
||||
+ for name in ('bwrap','spawner'):
|
||||
+ diagnostic=fixture/(name+'.stderr')
|
||||
+ if diagnostic.exists():
|
||||
+ with diagnostic.open('rb') as output:
|
||||
+ content=output.read(8193)
|
||||
+ receipt[name+'_stderr_public']=content[:8192].decode('utf-8','replace')
|
||||
+ receipt[name+'_stderr_truncated']=len(content)>8192
|
||||
if fixture.exists() or set(Path('/dev/shm').glob('rmgr-secret-scan-*'))-previous:
|
||||
raise p.Refused('outer-storage-cleanup-unverified')
|
||||
receipt.update(status='verified',fixture_removed=True,new_scan_storage_absent=True)
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Hard-loss fixture targets only a newly spawned generated-data process tree."""
|
||||
import argparse
|
||||
import functools
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import select
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
spec = importlib.util.spec_from_file_location('namespace_profile', HERE/'probe_namespace_hooks.py')
|
||||
p = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(p)
|
||||
|
||||
|
||||
def adapter():
|
||||
return p.load_bound(p.SOURCE, p.SOURCE_SHA, 'reviewed_lifecycle_storage')
|
||||
|
||||
|
||||
def inner(path):
|
||||
data = json.loads(path.read_text())
|
||||
current = p.namespaces()
|
||||
if (p.identity(Path('/')) != data['root'] or p.identity(Path('/dev/shm')) != data['shm']
|
||||
or current['pid'] != data['ns']['pid'] or current['mnt'] == data['ns']['mnt']
|
||||
or current['user'] == data['ns']['user'] or current['net'] == data['ns']['net']
|
||||
or p.sha(Path('/usr/bin/python3')) != data['interpreter_sha256']):
|
||||
return 23
|
||||
a = adapter()
|
||||
with a.private_storage() as (area, watcher):
|
||||
(area/'public-generated-sentinel').write_bytes(b'public generated parent-loss sentinel')
|
||||
readback = {'storage':str(area), 'storage_identity':p.identity(area),
|
||||
'inner_pid':os.getpid(), 'watcher_pid':watcher.pid,
|
||||
'namespace_identity_verified':True}
|
||||
temporary = Path(data['readback'] + '.tmp')
|
||||
temporary.write_text(json.dumps(readback))
|
||||
temporary.replace(Path(data['readback']))
|
||||
time.sleep(30)
|
||||
return 24 # The observer must kill the disposable spawner before normal expiry.
|
||||
|
||||
|
||||
def spawn(path):
|
||||
a = adapter()
|
||||
data = json.loads(path.read_text())
|
||||
env = {'PATH':'/usr/bin:/bin','HOME':'/home/worsch','LANG':'C'}
|
||||
args = p.command(Path('/home/worsch/repo-manager'), env, path)
|
||||
args = args[:args.index('--')+1] + ['/usr/bin/python3','-I',str(Path(__file__).resolve()),'--inner',str(path)]
|
||||
child = subprocess.Popen(args, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
preexec_fn=functools.partial(a.child_limits,64*1024**2,os.getpid()))
|
||||
try:
|
||||
Path(data['bwrap_pid']).write_text(str(child.pid))
|
||||
return child.wait(timeout=30)
|
||||
finally:
|
||||
if child.poll() is None:
|
||||
child.kill(); child.wait(timeout=5)
|
||||
|
||||
|
||||
def main():
|
||||
receipt={'status':'unverified','scope':'generated namespace hard-parent-loss process tree only',
|
||||
'real_index_opened':False,'real_credentials_read':False,'provider_calls':0,
|
||||
'host_scanner_modified':False}
|
||||
descriptors={}; child=None; fixture=None; inner_area=None
|
||||
try:
|
||||
if p.sha(p.BWRAP) != p.BWRAP_SHA:
|
||||
raise p.Refused('bwrap-drift')
|
||||
a=adapter()
|
||||
previous=set(Path('/dev/shm').glob('rmgr-secret-scan-*'))
|
||||
with a.private_storage() as (fixture, outer_watcher):
|
||||
data={'root':p.identity(Path('/')),'shm':p.identity(Path('/dev/shm')),
|
||||
'ns':p.namespaces(),'readback':str(fixture/'inner.readback'),
|
||||
'bwrap_pid':str(fixture/'bwrap.pid'),
|
||||
'interpreter_sha256':p.sha(Path('/usr/bin/python3'))}
|
||||
path=fixture/'spec.json'; path.write_text(json.dumps(data))
|
||||
child=subprocess.Popen([sys.executable,'-I',str(Path(__file__).resolve()),'--spawn',str(path)],
|
||||
env={'PATH':'/usr/bin:/bin','HOME':'/home/worsch','LANG':'C'},
|
||||
stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,
|
||||
preexec_fn=functools.partial(a.child_limits,64*1024**2,os.getpid()))
|
||||
try:
|
||||
descriptors['spawner']=os.pidfd_open(child.pid)
|
||||
until=time.monotonic()+8
|
||||
readback=Path(data['readback'])
|
||||
while not readback.exists() and child.poll() is None and time.monotonic()<until:
|
||||
time.sleep(.02)
|
||||
if not readback.exists():
|
||||
raise p.Refused('namespace-lifecycle-preflight-failed')
|
||||
body=json.loads(readback.read_text()); inner_area=Path(body['storage'])
|
||||
if (not body['namespace_identity_verified'] or p.identity(inner_area)!=body['storage_identity']
|
||||
or not str(inner_area).startswith('/dev/shm/rmgr-secret-scan-')):
|
||||
raise p.Refused('lifecycle-readback-refused')
|
||||
descriptors['bwrap']=os.pidfd_open(int(Path(data['bwrap_pid']).read_text()))
|
||||
descriptors['inner']=os.pidfd_open(body['inner_pid'])
|
||||
descriptors['cleanup_observer']=os.pidfd_open(body['watcher_pid'])
|
||||
# Exactly this new disposable spawner is killed. The calling tool,
|
||||
# shell, test harness, observer and their process groups are untouched.
|
||||
signal.pidfd_send_signal(descriptors['spawner'],signal.SIGKILL)
|
||||
child.wait(timeout=5)
|
||||
until=time.monotonic()+8
|
||||
pending=set(descriptors.values())
|
||||
while pending and time.monotonic()<until:
|
||||
ready,_,_=select.select(list(pending),[],[],max(0,until-time.monotonic()))
|
||||
pending.difference_update(ready)
|
||||
if pending or inner_area.exists():
|
||||
raise p.Refused('namespace-hard-loss-cleanup-unverified')
|
||||
receipt.update(namespace_identity_verified=True, exact_disposable_parent_killed=True,
|
||||
bwrap_inner_and_cleanup_observer_death_verified=True,inner_storage_removed=True)
|
||||
finally:
|
||||
# Quiesce named source/readback creators before the outer storage
|
||||
# context can remove their directory. Never kill the independent
|
||||
# cleanup observer; it owns the inner storage recovery duty.
|
||||
for name in ('spawner', 'bwrap', 'inner'):
|
||||
descriptor = descriptors.get(name)
|
||||
if descriptor is None:
|
||||
continue
|
||||
dead, _, _ = select.select([descriptor], [], [], 0)
|
||||
if not dead:
|
||||
try:
|
||||
signal.pidfd_send_signal(descriptor, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
# Retain the quiescence duty until exact death is proven.
|
||||
select.select([descriptor], [], [])
|
||||
if child.poll() is None:
|
||||
child.kill()
|
||||
child.wait(timeout=5)
|
||||
observer = descriptors.get('cleanup_observer')
|
||||
if observer is not None:
|
||||
select.select([observer], [], [], 8)
|
||||
if fixture.exists() or set(Path('/dev/shm').glob('rmgr-secret-scan-*'))-previous:
|
||||
raise p.Refused('outer-storage-cleanup-unverified')
|
||||
receipt.update(status='verified',fixture_removed=True,new_scan_storage_absent=True)
|
||||
except Exception as error:
|
||||
receipt['reason']=error.args[0] if isinstance(error,p.Refused) else 'lifecycle-probe-unavailable'
|
||||
finally:
|
||||
if child is not None and child.poll() is None:
|
||||
try:
|
||||
signal.pidfd_send_signal(descriptors['spawner'],signal.SIGKILL)
|
||||
child.wait(timeout=5)
|
||||
except (OSError,subprocess.SubprocessError):
|
||||
pass
|
||||
for fd in descriptors.values():
|
||||
os.close(fd)
|
||||
receipt['fixture_removed']=fixture is None or not fixture.exists()
|
||||
receipt['inner_storage_removed']=inner_area is None or not inner_area.exists()
|
||||
receipt['driver_sha256']=p.sha(Path(__file__))
|
||||
(HERE/'namespace-lifecycle-results.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print(json.dumps({'status':receipt['status'],'fixture_removed':receipt['fixture_removed'],
|
||||
'inner_storage_removed':receipt['inner_storage_removed']}))
|
||||
return 0 if receipt['status']=='verified' else 2
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
parser=argparse.ArgumentParser();parser.add_argument('--spawn',type=Path);parser.add_argument('--inner',type=Path)
|
||||
args=parser.parse_args()
|
||||
raise SystemExit(spawn(args.spawn) if args.spawn else inner(args.inner) if args.inner else main())
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
{
|
||||
"state": "R3 coordinator-executed fixture verified; owner completion patch prepared for root review, not applied",
|
||||
"owner_head": "b02e1cefb0e07fe246dc9fe9c5a989fe06116587",
|
||||
"source_sha256": "e709c658f779534fcab093207b0a78e49d54fc584c515fdf7ac535ce75f6c400",
|
||||
"scanner_sha256": "88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509",
|
||||
"hook_sha256": "d50cf763dc9814383c6886e30b6650bbbdcb49ddd78889082e3fda3626a2cbfd",
|
||||
"bwrap_sha256": "e318903862396f96de3df57264e0158682b952fd3fb53ac23d876413e7b30f71",
|
||||
"files": {
|
||||
"probe_namespace_hooks.py": "ff276bae336a560d365ed1255f9af97b4207294574df9bcf74543dc5c0678e35",
|
||||
"probe_namespace_lifecycle.py": "9c1db89b4379a7e55a72eba58efb3dfdae38d3ce69349d1bc1429465bc8cb5c5",
|
||||
"test_namespace_profile.py": "dec2b0bdb5bfaad532f8151fa07ce975c9be48ea73cfc1bfc14f7c0151058180",
|
||||
"run_reviewed_fixture.sh": "07d0b518a4e99c6d88c0cbc2a03c76b20429f0ac7d86d3985ca46528cbffe596"
|
||||
},
|
||||
"expected_hook_cases": 6,
|
||||
"unit_profile_checks_passed": 10,
|
||||
"namespace_policy": {
|
||||
"changed": [
|
||||
"user",
|
||||
"mount",
|
||||
"network"
|
||||
],
|
||||
"preserved": [
|
||||
"pid",
|
||||
"root dev/inode",
|
||||
"dev/shm dev/inode",
|
||||
"uid/gid"
|
||||
],
|
||||
"writable_bind_only": "/dev/shm to existing /dev/shm",
|
||||
"scanner_shadow": "new generated empty0400file readonlybound over exact admitted scanner path only for unavailable cases",
|
||||
"device_bind_exception": "/dev/null only; preserve host dev/inode/rdev, verify character type and actualO_RDWR|O_NOFOLLOW open"
|
||||
},
|
||||
"expected_unavailable_result": {
|
||||
"status": "unverified",
|
||||
"exit_code": 2,
|
||||
"reason": "scanner-file-refused",
|
||||
"scanned_blobs": 0
|
||||
},
|
||||
"forbidden_actions": [
|
||||
"host scanner chmod/bytes changes",
|
||||
"hook/source/config changes",
|
||||
"real index open/write",
|
||||
"real source blob scan",
|
||||
"network/provider calls",
|
||||
"new install",
|
||||
"production configuration",
|
||||
"external messages"
|
||||
],
|
||||
"cleanup": "reviewed canonical tmpfs+pidfd watchdog; bwrapdie-with-parent; source childrenPDEATHSIG; actual namespace hard-loss fixture first, exact disposable spawner only, independent cleanupobserver survives",
|
||||
"bounds": {
|
||||
"git_seconds": 20,
|
||||
"hook_case_seconds": 30,
|
||||
"child_cpu_seconds": 20,
|
||||
"child_virtual_address_GiB": 8,
|
||||
"generated_git_file_limit_MiB": 16,
|
||||
"bwrap_hook_file_limit_MiB": 64,
|
||||
"outer_watchdog_seconds": 180,
|
||||
"watchdog_TERM_grace_seconds": 5,
|
||||
"uncertain_kernel_quiescence": "retain observer duty until exact pidfddeath; never delete under known live creator"
|
||||
},
|
||||
"canonical_mutations_performed": false,
|
||||
"runtime_fixture_executed": true,
|
||||
"registration_gate": "atomic inner PID+namespace early-ready; main opens exact bwrap+inner pidfds and verifies live before writing exact generated gate; inner refuses storage after8seconds without release",
|
||||
"superseded_lifecycle_sha256": "7b74d96c867d44a0e994890d58d0d039028684ded3611ffb65f9c7b09d6f5527",
|
||||
"failed_r1": {
|
||||
"driver_sha256": "c46efb37e02349ba73f83b46c8fd8b1abc286282be8dc0a105ae76911e1ca266",
|
||||
"result": "namespace-lifecycle-preflight-failed",
|
||||
"fixture_removed": true,
|
||||
"inner_storage_removed": true,
|
||||
"raw_packet": "failed-r1/",
|
||||
"stderr_history": "R1 discarded spawner/bwrap stderr; cannot recover retroactively"
|
||||
},
|
||||
"r2_delta": [
|
||||
"atomic release-gate publication",
|
||||
"fixed public phase readback with exceptionclass/errno/whitelisted source reason",
|
||||
"lifecycle-only bwrap/spawner stderr capture capped8192bytes each; file growth capped1MiB per child",
|
||||
"no canonical or actual-hook driver changes"
|
||||
],
|
||||
"failed_r2": {
|
||||
"driver_sha256": "9021bd7a6a52ebe4ecd52587094b793a967f2578e24c6a30ed7b81c4a9154f52",
|
||||
"last_inner_phase": {
|
||||
"phase": "inner-exception",
|
||||
"exception_type": "PermissionError",
|
||||
"errno": 13
|
||||
},
|
||||
"stderr_empty": true,
|
||||
"cleanup_true": true,
|
||||
"raw_packet": "failed-r2/",
|
||||
"cause": "unverified; narrow /dev/null successor under review"
|
||||
},
|
||||
"r3_delta": [
|
||||
"readonlyroot unchanged; add onlydev-bind/dev/null\u2192/dev/null",
|
||||
"verify nullcharacter type+hostdevice/inode/rdev+actualO_RDWR open inside namespace",
|
||||
"preserve last_successful_phase and filenameonlyifliteral/dev/null onexception",
|
||||
"same gate/lifecycle/scriptpins, no hostscanner/source/hook mutation"
|
||||
],
|
||||
"validation": {
|
||||
"unit_profile_checks": 10,
|
||||
"namespace_lifecycle_verified": true,
|
||||
"actual_configured_hook_cases": 6,
|
||||
"root_and_shm_identity_readback_verified": true,
|
||||
"source_hook_interpreter_hash_readback_verified": true,
|
||||
"host_scanner_stat_hash_mode_unchanged": true,
|
||||
"fixtures_removed": true,
|
||||
"new_scan_storage_absent": true,
|
||||
"real_index_opened": false,
|
||||
"real_index_stat_and_HEAD_unchanged": true
|
||||
},
|
||||
"receipts": {
|
||||
"profile-test-results.txt": "a837324a2f5c0d804ee26513e45c3e17425242b25aba8e53fe57a7379e8a79a5",
|
||||
"namespace-lifecycle-results.json": "7250def507f30c164499738bb3aa65668c27853319370b34eef6a41685523dc4",
|
||||
"namespace-hook-results.json": "5aaef79c0be4ae3cf721482784b255dde795559363bfb671e64ba6252fec59bc"
|
||||
},
|
||||
"owner_completion": {
|
||||
"base_commit": "b02e1cefb0e07fe246dc9fe9c5a989fe06116587",
|
||||
"base_file_sha256": "a75a106b77fe481b45e5d13593261f8a540c7eafeda1b40c0b2116ecdc9ee816",
|
||||
"result_file_sha256": "966d15ccdabc8edf98bc58f9422ea190566d05fe4208f4749f59a9731f4e108d",
|
||||
"patch_sha256": "0e7a98972fd6863ba839c000669d167e36b5ae3257a52db285e5ffbe03a7a04c",
|
||||
"task_status_changes": {
|
||||
"RMGR-WP-0021-T05": "progress\u2192done"
|
||||
},
|
||||
"T03": "progress",
|
||||
"T04": "todo",
|
||||
"workplan_status": "active",
|
||||
"canonical_mutation_performed": false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
--- a/probe_namespace_hooks.py
|
||||
+++ b/probe_namespace_hooks.py
|
||||
@@ -45,6 +45,24 @@
|
||||
return [item.st_dev, item.st_ino]
|
||||
|
||||
|
||||
+def null_identity():
|
||||
+ item = os.stat('/dev/null')
|
||||
+ return [item.st_dev, item.st_ino, item.st_rdev, stat.S_ISCHR(item.st_mode)]
|
||||
+
|
||||
+
|
||||
+def check_null(expected):
|
||||
+ if null_identity() != expected or not expected[3]:
|
||||
+ raise Refused('null-device-identity-refused')
|
||||
+ descriptor = os.open(os.devnull, os.O_RDWR | os.O_NOFOLLOW)
|
||||
+ try:
|
||||
+ item = os.fstat(descriptor)
|
||||
+ if [item.st_dev,item.st_ino,item.st_rdev,stat.S_ISCHR(item.st_mode)] != expected:
|
||||
+ raise Refused('null-device-open-identity-refused')
|
||||
+ finally:
|
||||
+ os.close(descriptor)
|
||||
+ return {'character_device':True,'host_identity_and_rdev_preserved':True,'read_write_open_verified':True}
|
||||
+
|
||||
+
|
||||
def namespaces():
|
||||
return {key: os.readlink('/proc/self/ns/' + key) for key in ('user', 'mnt', 'pid', 'net')}
|
||||
|
||||
@@ -62,6 +80,7 @@
|
||||
def command(root, env, spec_path, shadow=None):
|
||||
argv = [str(BWRAP), '--unshare-user', '--unshare-net', '--die-with-parent', '--clearenv',
|
||||
'--ro-bind', '/', '/', '--bind', '/dev/shm', '/dev/shm',
|
||||
+ '--dev-bind', '/dev/null', '/dev/null',
|
||||
'--uid', str(os.getuid()), '--gid', str(os.getgid()), '--chdir', str(root)]
|
||||
if shadow is not None:
|
||||
argv += ['--ro-bind', str(shadow), str(BINARY)]
|
||||
@@ -73,7 +92,7 @@
|
||||
|
||||
def check_spec(spec):
|
||||
if set(spec) != {'root', 'hook', 'shadow', 'readback', 'host_root', 'host_shm',
|
||||
- 'host_ns', 'uid', 'gid', 'interpreter_sha256', 'driver_sha256'}:
|
||||
+ 'host_ns', 'host_null', 'uid', 'gid', 'interpreter_sha256', 'driver_sha256'}:
|
||||
raise Refused('spec-schema-refused')
|
||||
allowed = {
|
||||
'/home/worsch/repo-manager': '/home/worsch/repo-manager/.githooks/pre-commit',
|
||||
@@ -103,6 +122,7 @@
|
||||
or not (os.statvfs('/').f_flag & os.ST_RDONLY)
|
||||
or os.statvfs('/dev/shm').f_flag & os.ST_RDONLY):
|
||||
raise Refused('namespace-identity-refused')
|
||||
+ null_readback = check_null(spec['host_null'])
|
||||
if (sha(SOURCE) != SOURCE_SHA or sha(Path(spec['hook'])) != HOOK_SHA
|
||||
or sha(Path(__file__).resolve()) != spec['driver_sha256']
|
||||
or shutil.which('python3') != '/usr/bin/python3'
|
||||
@@ -119,7 +139,7 @@
|
||||
record = {'verified': True, 'root_identity_preserved': True,
|
||||
'shm_identity_preserved': True, 'root_readonly': True, 'shm_writable': True,
|
||||
'user_mount_network_namespaces_changed': True, 'pid_namespace_preserved': True,
|
||||
- 'uid_gid_preserved': True, 'source_sha256': SOURCE_SHA,
|
||||
+ 'uid_gid_preserved': True, 'null_device_readback':null_readback, 'source_sha256': SOURCE_SHA,
|
||||
'hook_sha256': HOOK_SHA, 'interpreter_sha256': spec['interpreter_sha256'],
|
||||
'scanner_view': 'empty-0400-shadow' if spec['shadow'] else 'pinned-real-scanner'}
|
||||
target = Path(spec['readback'])
|
||||
@@ -201,7 +221,7 @@
|
||||
readback_path = local / (case+'.readback.json')
|
||||
spec = {'root': str(root), 'hook': str(hook), 'shadow': unavailable,
|
||||
'readback': str(readback_path), 'host_root': host_root, 'host_shm': host_shm,
|
||||
- 'host_ns': host_ns, 'uid': os.getuid(), 'gid': os.getgid(),
|
||||
+ 'host_ns': host_ns, 'host_null': null_identity(), 'uid': os.getuid(), 'gid': os.getgid(),
|
||||
'interpreter_sha256': interpreter_sha, 'driver_sha256': sha(Path(__file__))}
|
||||
spec_path.write_text(json.dumps(spec))
|
||||
result = subprocess.run(command(root, probe_env, spec_path,
|
||||
--- a/probe_namespace_lifecycle.py
|
||||
+++ b/probe_namespace_lifecycle.py
|
||||
@@ -27,12 +27,17 @@
|
||||
return
|
||||
record = {'phase':name}
|
||||
if error is not None:
|
||||
+ record['last_successful_phase']=data.get('_last_successful_phase')
|
||||
+ if getattr(error,'filename',None) == '/dev/null':
|
||||
+ record['fixed_filename']='/dev/null'
|
||||
record['exception_type'] = type(error).__name__
|
||||
record['errno'] = getattr(error, 'errno', None)
|
||||
allowed = {'volatile-storage-unavailable','cleanup-watchdog-unavailable',
|
||||
'volatile-storage-identity-changed','cleanup-watchdog-failed'}
|
||||
if error.args and isinstance(error.args[0],str) and error.args[0] in allowed:
|
||||
record['fixed_source_reason'] = error.args[0]
|
||||
+ if error is None:
|
||||
+ data['_last_successful_phase']=name
|
||||
target=Path(data['phase_readback']); temporary=target.with_suffix('.tmp')
|
||||
temporary.write_text(json.dumps(record)); temporary.replace(target)
|
||||
|
||||
@@ -55,6 +60,8 @@
|
||||
or p.sha(Path('/usr/bin/python3')) != data['interpreter_sha256']):
|
||||
return 23
|
||||
phase(data, 'namespace-verified')
|
||||
+ null_readback = p.check_null(data['host_null'])
|
||||
+ phase(data, 'null-device-opened')
|
||||
# Register the exact inner process before any source-bearing storage can
|
||||
# exist. Publication is atomic; the only earlier effect is PID metadata.
|
||||
early = Path(data['early_ready'])
|
||||
@@ -77,7 +84,7 @@
|
||||
phase(data, 'sentinel-written')
|
||||
readback = {'storage':str(area), 'storage_identity':p.identity(area),
|
||||
'inner_pid':os.getpid(), 'watcher_pid':watcher.pid,
|
||||
- 'namespace_identity_verified':True}
|
||||
+ 'namespace_identity_verified':True, 'null_device_readback':null_readback}
|
||||
temporary = Path(data['readback'] + '.tmp')
|
||||
temporary.write_text(json.dumps(readback))
|
||||
temporary.replace(Path(data['readback']))
|
||||
@@ -118,7 +125,7 @@
|
||||
previous=set(Path('/dev/shm').glob('rmgr-secret-scan-*'))
|
||||
with a.private_storage() as (fixture, outer_watcher):
|
||||
data={'root':p.identity(Path('/')),'shm':p.identity(Path('/dev/shm')),
|
||||
- 'ns':p.namespaces(),'readback':str(fixture/'inner.readback'),
|
||||
+ 'ns':p.namespaces(),'host_null':p.null_identity(),'readback':str(fixture/'inner.readback'),
|
||||
'bwrap_pid':str(fixture/'bwrap.pid'),
|
||||
'early_ready':str(fixture/'inner.early.ready'),
|
||||
'phase_readback':str(fixture/'inner.phase.json'),
|
||||
@@ -179,7 +186,7 @@
|
||||
if pending or inner_area.exists():
|
||||
raise p.Refused('namespace-hard-loss-cleanup-unverified')
|
||||
receipt.update(namespace_identity_verified=True, pre_effect_pidfd_registration_verified=True,
|
||||
- exact_disposable_parent_killed=True,
|
||||
+ exact_disposable_parent_killed=True, null_device_readback=body['null_device_readback'],
|
||||
bwrap_inner_and_cleanup_observer_death_verified=True,inner_storage_removed=True)
|
||||
finally:
|
||||
# Quiesce named source/readback creators before the outer storage
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
--- a/workplans/RMGR-WP-0021-secret-detection-controls.md
|
||||
+++ b/workplans/RMGR-WP-0021-secret-detection-controls.md
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
```task
|
||||
id: RMGR-WP-0021-T05
|
||||
-status: progress
|
||||
+status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "25fbe694-41d8-5232-bce0-4ed362387d5a"
|
||||
```
|
||||
@@ -204,3 +204,40 @@
|
||||
through rmgr sync --push and central primary/railiance01 accepted source projection;
|
||||
no global installed CLI is claimed. T03/T04 and whole target gates remain open.
|
||||
Final coordinator evidence: resume-integration-r1/publication-verification.json.
|
||||
+
|
||||
+
|
||||
+2026-10-17 bounded T05 completion evidence: the independently reviewed R3 namespace
|
||||
+fixture passed ten profile checks, exact-pidfd hard-parent-loss and cleanup
|
||||
+acceptance, and six actual configured-hook cases. Both the shared repo-manager
|
||||
+hook and Custodian hook accepted a clean generated blob (exit 0), blocked a
|
||||
+freshly generated private-key fixture (exit 1), and refused a non-executable
|
||||
+scanner view (exit 2, `unverified`, `scanner-file-refused`, zero scanned blobs).
|
||||
+The unavailable view was a new empty 0400 file read-only-bound over the exact
|
||||
+configured scanner path inside fresh user/mount/network namespaces; the host
|
||||
+scanner's bytes, stat identity and mode were unchanged. The canonical source,
|
||||
+hooks and interpreter hashes were read back before exec, original root and
|
||||
+`/dev/shm` device/inode identities and UID/GID were preserved, root was read only,
|
||||
+and only original `/dev/shm` and exact `/dev/null` had the reviewed writable/device
|
||||
+bind exceptions. The null character device and actual O_RDWR open were verified.
|
||||
+PID namespace identity stayed shared so the canonical independent cleanup
|
||||
+observer survived parent loss. Real indexes were not opened, real index stat
|
||||
+identities and HEADs stayed unchanged, and all new volatile storage was absent.
|
||||
+
|
||||
+Retain the failed preparation history: R1 refused after early process
|
||||
+registration without a full readback; R2 recorded PermissionError errno 13,
|
||||
+without enough retained phase information to establish its exact cause. Both
|
||||
+failed revisions and receipts are in `failed-r1/` and `failed-r2/`. R3 atomically
|
||||
+published the release gate, retained fixed public phase diagnostics and verified
|
||||
+the narrow `/dev/null` device bind and open. Its successful result does not
|
||||
+retroactively assign a verified cause or success to R1/R2.
|
||||
+
|
||||
+Receipts: Custodian
|
||||
+`docs/changes/netkingdom-target-2026-10-17/scanner-unavailable-r1/namespace-lifecycle-results.json`
|
||||
+and `namespace-hook-results.json`, with exact driver hashes and manifest.
|
||||
+Earlier publication and explicit repo-local CLI acceptance remain recorded above;
|
||||
+no globally installed CLI is claimed. This completes the bounded local staged
|
||||
+scanner optimization and its configured-hook generated-fixture acceptance. It
|
||||
+proves the exact non-executable scanner view within this namespace fixture, not
|
||||
+an observed host outage or coverage of other machines. T03 remains progress,
|
||||
+T04 remains todo, the workplan remains active, and remote receive/CI, broader
|
||||
+source-control coverage, real finding response and whole TSA15 remain open.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"exit_code": 0,
|
||||
"stdout": "[main 9ebde0a] docs(security): accept bounded staged scanner runtime fixtures\n 1 file changed, 38 insertions(+), 1 deletion(-)\n",
|
||||
"stderr": "{\"blocked_blob_ids\": [], \"scanned_blobs\": 1, \"scanner_sha256\": \"88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509\", \"scanner_version\": \"8.30.1\", \"scope\": \"changed-staged-blobs\", \"status\": \"clean\"}\n",
|
||||
"head": "9ebde0a9bd60d5c82910494fbe6bd1b891455cee"
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"ok": true,
|
||||
"status": "pending_push",
|
||||
"message": "commit is not yet visible to the authoritative forge",
|
||||
"git": {
|
||||
"upstream": "origin/main",
|
||||
"behind": 0,
|
||||
"ahead": 1
|
||||
},
|
||||
"identifiers": {
|
||||
"schema": "repo-manager.identifier-assignment.v1",
|
||||
"ok": true,
|
||||
"executed": true,
|
||||
"repo": "repo-manager",
|
||||
"namespace": "helixforge",
|
||||
"assignments": [],
|
||||
"historical_invalid_identifiers": [],
|
||||
"files_changed": []
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generated alternate-index hook probes; no host scanner/hook/source mutation.
|
||||
|
||||
Only persistent writes are fixed-metadata receipts in this review packet.
|
||||
User+mount namespaces shadow the exact scanner path; PID namespace stays shared
|
||||
so the admitted canonical scanner's independent cleanup watchdog can survive.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import types
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
BASE = HERE.parent.parent / 'netkingdom-target-2026-10-16/scanner-batch-r1/probe_configured_hooks.py'
|
||||
BASE_SHA = 'c91d2dc83f04ec17147c0e1410e42f4817b69e4cc5a02201682e5fa7b021b6f4'
|
||||
BWRAP = Path('/usr/bin/bwrap')
|
||||
BWRAP_SHA = 'e318903862396f96de3df57264e0158682b952fd3fb53ac23d876413e7b30f71'
|
||||
OWNER_HEAD = b'b02e1cefb0e07fe246dc9fe9c5a989fe06116587'
|
||||
SOURCE = Path('/home/worsch/repo-manager/src/repo_manager/secret_scan.py')
|
||||
SOURCE_SHA = 'e709c658f779534fcab093207b0a78e49d54fc584c515fdf7ac535ce75f6c400'
|
||||
BINARY = Path('/home/worsch/.local/lib/repo-manager/gitleaks/8.30.1/gitleaks')
|
||||
BINARY_SHA = '88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509'
|
||||
HOOK_SHA = 'd50cf763dc9814383c6886e30b6650bbbdcb49ddd78889082e3fda3626a2cbfd'
|
||||
|
||||
|
||||
class Refused(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def identity(path):
|
||||
item = path.stat()
|
||||
return [item.st_dev, item.st_ino]
|
||||
|
||||
|
||||
def null_identity():
|
||||
item = os.stat('/dev/null')
|
||||
return [item.st_dev, item.st_ino, item.st_rdev, stat.S_ISCHR(item.st_mode)]
|
||||
|
||||
|
||||
def check_null(expected):
|
||||
if null_identity() != expected or not expected[3]:
|
||||
raise Refused('null-device-identity-refused')
|
||||
descriptor = os.open(os.devnull, os.O_RDWR | os.O_NOFOLLOW)
|
||||
try:
|
||||
item = os.fstat(descriptor)
|
||||
if [item.st_dev,item.st_ino,item.st_rdev,stat.S_ISCHR(item.st_mode)] != expected:
|
||||
raise Refused('null-device-open-identity-refused')
|
||||
finally:
|
||||
os.close(descriptor)
|
||||
return {'character_device':True,'host_identity_and_rdev_preserved':True,'read_write_open_verified':True}
|
||||
|
||||
|
||||
def namespaces():
|
||||
return {key: os.readlink('/proc/self/ns/' + key) for key in ('user', 'mnt', 'pid', 'net')}
|
||||
|
||||
|
||||
def load_bound(path, digest, name):
|
||||
content = path.read_bytes()
|
||||
if hashlib.sha256(content).hexdigest() != digest:
|
||||
raise Refused('bound-source-drift')
|
||||
module = types.ModuleType(name)
|
||||
module.__file__ = str(path)
|
||||
exec(compile(content, str(path), 'exec'), module.__dict__)
|
||||
return module
|
||||
|
||||
|
||||
def command(root, env, spec_path, shadow=None):
|
||||
argv = [str(BWRAP), '--unshare-user', '--unshare-net', '--die-with-parent', '--clearenv',
|
||||
'--ro-bind', '/', '/', '--bind', '/dev/shm', '/dev/shm',
|
||||
'--dev-bind', '/dev/null', '/dev/null',
|
||||
'--uid', str(os.getuid()), '--gid', str(os.getgid()), '--chdir', str(root)]
|
||||
if shadow is not None:
|
||||
argv += ['--ro-bind', str(shadow), str(BINARY)]
|
||||
for key, value in sorted(env.items()):
|
||||
argv += ['--setenv', key, value]
|
||||
argv += ['--', '/usr/bin/python3', '-I', str(Path(__file__).resolve()), '--inside', str(spec_path)]
|
||||
return argv
|
||||
|
||||
|
||||
def check_spec(spec):
|
||||
if set(spec) != {'root', 'hook', 'shadow', 'readback', 'host_root', 'host_shm',
|
||||
'host_ns', 'host_null', 'uid', 'gid', 'interpreter_sha256', 'driver_sha256'}:
|
||||
raise Refused('spec-schema-refused')
|
||||
allowed = {
|
||||
'/home/worsch/repo-manager': '/home/worsch/repo-manager/.githooks/pre-commit',
|
||||
'/home/worsch/the-custodian': '/home/worsch/the-custodian/.git/hooks/pre-commit',
|
||||
}
|
||||
if allowed.get(spec['root']) != spec['hook'] or not isinstance(spec['shadow'], bool):
|
||||
raise Refused('spec-target-refused')
|
||||
destination = Path(spec['readback'])
|
||||
if not str(destination).startswith('/dev/shm/rmgr-secret-scan-'):
|
||||
raise Refused('readback-storage-refused')
|
||||
|
||||
|
||||
def inside(spec_path):
|
||||
# All targets originate in the fixed reviewed outer driver. Refuse changes
|
||||
# before executing a hook; only fixed metadata is written to volatile storage.
|
||||
try:
|
||||
spec = json.loads(spec_path.read_text())
|
||||
check_spec(spec)
|
||||
observed_ns = namespaces()
|
||||
if (identity(Path('/')) != spec['host_root']
|
||||
or identity(Path('/dev/shm')) != spec['host_shm']
|
||||
or os.getuid() != spec['uid'] or os.getgid() != spec['gid']
|
||||
or observed_ns['user'] == spec['host_ns']['user']
|
||||
or observed_ns['mnt'] == spec['host_ns']['mnt']
|
||||
or observed_ns['net'] == spec['host_ns']['net']
|
||||
or observed_ns['pid'] != spec['host_ns']['pid']
|
||||
or not (os.statvfs('/').f_flag & os.ST_RDONLY)
|
||||
or os.statvfs('/dev/shm').f_flag & os.ST_RDONLY):
|
||||
raise Refused('namespace-identity-refused')
|
||||
null_readback = check_null(spec['host_null'])
|
||||
if (sha(SOURCE) != SOURCE_SHA or sha(Path(spec['hook'])) != HOOK_SHA
|
||||
or sha(Path(__file__).resolve()) != spec['driver_sha256']
|
||||
or shutil.which('python3') != '/usr/bin/python3'
|
||||
or sha(Path('/usr/bin/python3')) != spec['interpreter_sha256']):
|
||||
raise Refused('namespace-code-readback-refused')
|
||||
scanner = BINARY.stat()
|
||||
if spec['shadow']:
|
||||
if (not stat.S_ISREG(scanner.st_mode) or scanner.st_size != 0
|
||||
or stat.S_IMODE(scanner.st_mode) != 0o400
|
||||
or sha(BINARY) != hashlib.sha256(b'').hexdigest()):
|
||||
raise Refused('scanner-shadow-readback-refused')
|
||||
elif sha(BINARY) != BINARY_SHA or stat.S_IMODE(scanner.st_mode) != 0o755:
|
||||
raise Refused('baseline-scanner-readback-refused')
|
||||
record = {'verified': True, 'root_identity_preserved': True,
|
||||
'shm_identity_preserved': True, 'root_readonly': True, 'shm_writable': True,
|
||||
'user_mount_network_namespaces_changed': True, 'pid_namespace_preserved': True,
|
||||
'uid_gid_preserved': True, 'null_device_readback':null_readback, 'source_sha256': SOURCE_SHA,
|
||||
'hook_sha256': HOOK_SHA, 'interpreter_sha256': spec['interpreter_sha256'],
|
||||
'scanner_view': 'empty-0400-shadow' if spec['shadow'] else 'pinned-real-scanner'}
|
||||
target = Path(spec['readback'])
|
||||
with target.open('x') as output:
|
||||
json.dump(record, output)
|
||||
os.execve(spec['hook'], [spec['hook']], dict(os.environ))
|
||||
except Exception:
|
||||
return 23
|
||||
|
||||
|
||||
def environment(base):
|
||||
env = base.fixed_env()
|
||||
env['GIT_CONFIG_GLOBAL'] = '/dev/null'
|
||||
overrides = [('core.fsmonitor', 'false'), ('core.splitIndex', 'false'),
|
||||
('core.sparseCheckout', 'false'), ('core.untrackedCache', 'false')]
|
||||
env['GIT_CONFIG_COUNT'] = str(len(overrides))
|
||||
for number, (key, value) in enumerate(overrides):
|
||||
env[f'GIT_CONFIG_KEY_{number}'] = key
|
||||
env[f'GIT_CONFIG_VALUE_{number}'] = value
|
||||
return env
|
||||
|
||||
|
||||
def main():
|
||||
receipt = {'status': 'unverified', 'scope': 'two actual configured hooks; generated alternate-index/object fixtures in mount/user/network namespaces',
|
||||
'provider_calls': 0, 'real_credentials_read': False, 'real_index_opened': False,
|
||||
'source_sha256': SOURCE_SHA, 'scanner_sha256': BINARY_SHA, 'hook_sha256': HOOK_SHA,
|
||||
'bwrap_sha256': BWRAP_SHA, 'host_scanner_modified': False, 'rows': []}
|
||||
fixture = None
|
||||
try:
|
||||
if sha(BWRAP) != BWRAP_SHA or BWRAP.stat().st_mode & 0o6022:
|
||||
raise Refused('namespace-binary-refused')
|
||||
base = load_bound(BASE, BASE_SHA, 'reviewed_hook_probe')
|
||||
records = base.precheck()
|
||||
if records[0]['head'] != OWNER_HEAD:
|
||||
raise Refused('owner-head-drift')
|
||||
before_binary = base.info(BINARY)
|
||||
adapter = load_bound(SOURCE, SOURCE_SHA, 'canonical_scanner_storage')
|
||||
prior_storage = set(Path('/dev/shm').glob('rmgr-secret-scan-*'))
|
||||
host_ns = namespaces()
|
||||
host_root = identity(Path('/')); host_shm = identity(Path('/dev/shm'))
|
||||
interpreter_sha = sha(Path('/usr/bin/python3'))
|
||||
with adapter.private_storage() as (fixture, watcher):
|
||||
env = environment(base)
|
||||
key_path = fixture / 'generated-key'
|
||||
base.run(['/usr/bin/ssh-keygen', '-q', '-t', 'ed25519', '-N', '', '-C',
|
||||
'public-generated-namespace-probe', '-f', str(key_path)], fixture, env,
|
||||
adapter=adapter)
|
||||
key = key_path.read_bytes()
|
||||
shadow = fixture / 'unavailable-scanner'
|
||||
with shadow.open('x'):
|
||||
pass
|
||||
shadow.chmod(0o400) # Only this newly created empty disposable file.
|
||||
for number, record in enumerate(records):
|
||||
root = record['root']; hook = record['hook']
|
||||
local = fixture / str(number); local.mkdir(mode=0o700)
|
||||
objects = local / 'objects'; objects.mkdir(mode=0o700)
|
||||
probe_env = dict(env, GIT_INDEX_FILE=str(local/'index'),
|
||||
GIT_OBJECT_DIRECTORY=str(objects), GIT_ALTERNATE_OBJECT_DIRECTORIES=record['objects'])
|
||||
name = '__rmgr_namespace_generated_fixture__.txt'
|
||||
if base.git(root, probe_env, 'ls-tree', '--name-only', 'HEAD', '--', name, adapter=adapter):
|
||||
raise Refused('fixture-name-collision')
|
||||
for case, payload, status, code, unavailable in [
|
||||
('clean-control', b'public namespace hook fixture\n', 'clean', 0, False),
|
||||
('generated-key-control', key, 'blocked', 1, False),
|
||||
('scanner-unavailable', b'public namespace unavailable fixture\n', 'unverified', 2, True),
|
||||
]:
|
||||
if watcher.poll() is not None:
|
||||
raise Refused('outer-watchdog-lost')
|
||||
base.git(root, probe_env, 'read-tree', 'HEAD', adapter=adapter)
|
||||
oid = base.git(root, probe_env, 'hash-object', '-w', '--stdin', data=payload,
|
||||
adapter=adapter).decode().strip()
|
||||
base.git(root, probe_env, 'update-index', '--add', '--cacheinfo',
|
||||
'100644,'+oid+','+name, adapter=adapter)
|
||||
changed = base.git(root, probe_env, 'diff', '--cached', '--name-only', '-z',
|
||||
'--no-renames', '--no-ext-diff', '--no-textconv', adapter=adapter)
|
||||
if changed != name.encode()+b'\0':
|
||||
raise Refused('changed-fixture-scope-refused')
|
||||
spec_path = local / (case+'.spec.json')
|
||||
readback_path = local / (case+'.readback.json')
|
||||
spec = {'root': str(root), 'hook': str(hook), 'shadow': unavailable,
|
||||
'readback': str(readback_path), 'host_root': host_root, 'host_shm': host_shm,
|
||||
'host_ns': host_ns, 'host_null': null_identity(), 'uid': os.getuid(), 'gid': os.getgid(),
|
||||
'interpreter_sha256': interpreter_sha, 'driver_sha256': sha(Path(__file__))}
|
||||
spec_path.write_text(json.dumps(spec))
|
||||
result = subprocess.run(command(root, probe_env, spec_path,
|
||||
shadow if unavailable else None), cwd=root, env=base.fixed_env(),
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=30, check=False,
|
||||
preexec_fn=functools.partial(adapter.child_limits, 64*1024**2, os.getpid()))
|
||||
if result.stderr or key.strip() in result.stdout or not readback_path.exists():
|
||||
raise Refused('namespace-or-hook-output-refused')
|
||||
readback = json.loads(readback_path.read_text())
|
||||
body = json.loads(result.stdout)
|
||||
if result.returncode != code or body.get('status') != status:
|
||||
raise Refused('hook-result-refused')
|
||||
if unavailable:
|
||||
if body != {'status': 'unverified', 'scope': 'changed-staged-blobs',
|
||||
'reason': 'scanner-file-refused', 'scanned_blobs': 0}:
|
||||
raise Refused('unavailable-summary-refused')
|
||||
elif body != {'status': status, 'scope': 'changed-staged-blobs',
|
||||
'scanner_version': '8.30.1', 'scanner_sha256': BINARY_SHA,
|
||||
'scanned_blobs': 1, 'blocked_blob_ids': [oid] if status=='blocked' else []}:
|
||||
raise Refused('control-summary-refused')
|
||||
receipt['rows'].append({'repo': root.name, 'case': case, 'status': status,
|
||||
'exit_code': code, 'scanned_blobs': body['scanned_blobs'],
|
||||
'reason': body.get('reason'), 'namespace_readback': readback})
|
||||
if fixture.exists() or set(Path('/dev/shm').glob('rmgr-secret-scan-*')) - prior_storage:
|
||||
raise Refused('volatile-cleanup-unverified')
|
||||
after_records = base.precheck()
|
||||
if (before_binary != base.info(BINARY)
|
||||
or any(a['index_stat'] != b['index_stat'] or a['head'] != b['head']
|
||||
for a, b in zip(records, after_records))):
|
||||
raise Refused('host-state-changed')
|
||||
receipt.update(status='verified', fixture_removed=True, new_scan_storage_absent=True,
|
||||
real_index_stat_unchanged=True, real_heads_unchanged=True,
|
||||
host_scanner_stat_hash_mode_unchanged=True, hook_source_routing_unchanged=True)
|
||||
except Exception as error:
|
||||
receipt['reason'] = error.args[0] if isinstance(error, Refused) else 'namespace-probe-unavailable'
|
||||
receipt['fixture_removed'] = fixture is None or not fixture.exists()
|
||||
receipt['driver_sha256'] = sha(Path(__file__))
|
||||
(HERE/'namespace-hook-results.json').write_text(json.dumps(receipt, indent=2)+'\n')
|
||||
print(json.dumps({'status':receipt['status'],'cases':len(receipt['rows']),
|
||||
'fixture_removed':receipt['fixture_removed']}))
|
||||
return 0 if receipt['status']=='verified' else 2
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser=argparse.ArgumentParser()
|
||||
parser.add_argument('--inside', type=Path)
|
||||
args=parser.parse_args()
|
||||
raise SystemExit(inside(args.inside) if args.inside else main())
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Hard-loss fixture targets only a newly spawned generated-data process tree."""
|
||||
import argparse
|
||||
import functools
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import select
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
spec = importlib.util.spec_from_file_location('namespace_profile', HERE/'probe_namespace_hooks.py')
|
||||
p = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(p)
|
||||
|
||||
|
||||
def adapter():
|
||||
return p.load_bound(p.SOURCE, p.SOURCE_SHA, 'reviewed_lifecycle_storage')
|
||||
|
||||
|
||||
def phase(data, name, error=None):
|
||||
if 'phase_readback' not in data:
|
||||
return
|
||||
record = {'phase':name}
|
||||
if error is not None:
|
||||
record['last_successful_phase']=data.get('_last_successful_phase')
|
||||
if getattr(error,'filename',None) == '/dev/null':
|
||||
record['fixed_filename']='/dev/null'
|
||||
record['exception_type'] = type(error).__name__
|
||||
record['errno'] = getattr(error, 'errno', None)
|
||||
allowed = {'volatile-storage-unavailable','cleanup-watchdog-unavailable',
|
||||
'volatile-storage-identity-changed','cleanup-watchdog-failed'}
|
||||
if error.args and isinstance(error.args[0],str) and error.args[0] in allowed:
|
||||
record['fixed_source_reason'] = error.args[0]
|
||||
if error is None:
|
||||
data['_last_successful_phase']=name
|
||||
target=Path(data['phase_readback']); temporary=target.with_suffix('.tmp')
|
||||
temporary.write_text(json.dumps(record)); temporary.replace(target)
|
||||
|
||||
|
||||
def inner(path):
|
||||
data=json.loads(path.read_text())
|
||||
try:
|
||||
phase(data, 'inner-started')
|
||||
return inner_body(data)
|
||||
except Exception as error:
|
||||
phase(data, 'inner-exception', error)
|
||||
return 26
|
||||
|
||||
|
||||
def inner_body(data):
|
||||
current = p.namespaces()
|
||||
if (p.identity(Path('/')) != data['root'] or p.identity(Path('/dev/shm')) != data['shm']
|
||||
or current['pid'] != data['ns']['pid'] or current['mnt'] == data['ns']['mnt']
|
||||
or current['user'] == data['ns']['user'] or current['net'] == data['ns']['net']
|
||||
or p.sha(Path('/usr/bin/python3')) != data['interpreter_sha256']):
|
||||
return 23
|
||||
phase(data, 'namespace-verified')
|
||||
null_readback = p.check_null(data['host_null'])
|
||||
phase(data, 'null-device-opened')
|
||||
# Register the exact inner process before any source-bearing storage can
|
||||
# exist. Publication is atomic; the only earlier effect is PID metadata.
|
||||
early = Path(data['early_ready'])
|
||||
temporary = early.with_suffix('.tmp')
|
||||
temporary.write_text(json.dumps({'inner_pid':os.getpid(), 'namespace_identity_verified':True}))
|
||||
temporary.replace(early)
|
||||
until = time.monotonic() + 8
|
||||
gate = Path(data['release_gate'])
|
||||
while not gate.exists() and time.monotonic() < until:
|
||||
time.sleep(.01)
|
||||
if not gate.exists() or gate.read_text() != data['gate_token']:
|
||||
phase(data, 'gate-token-refused')
|
||||
return 25
|
||||
phase(data, 'gate-released')
|
||||
a = adapter()
|
||||
phase(data, 'adapter-loaded')
|
||||
with a.private_storage() as (area, watcher):
|
||||
phase(data, 'storage-entered')
|
||||
(area/'public-generated-sentinel').write_bytes(b'public generated parent-loss sentinel')
|
||||
phase(data, 'sentinel-written')
|
||||
readback = {'storage':str(area), 'storage_identity':p.identity(area),
|
||||
'inner_pid':os.getpid(), 'watcher_pid':watcher.pid,
|
||||
'namespace_identity_verified':True, 'null_device_readback':null_readback}
|
||||
temporary = Path(data['readback'] + '.tmp')
|
||||
temporary.write_text(json.dumps(readback))
|
||||
temporary.replace(Path(data['readback']))
|
||||
phase(data, 'full-readback-published')
|
||||
time.sleep(30)
|
||||
return 24 # The observer must kill the disposable spawner before normal expiry.
|
||||
|
||||
|
||||
def spawn(path):
|
||||
a = adapter()
|
||||
data = json.loads(path.read_text())
|
||||
env = {'PATH':'/usr/bin:/bin','HOME':'/home/worsch','LANG':'C'}
|
||||
args = p.command(Path('/home/worsch/repo-manager'), env, path)
|
||||
args = args[:args.index('--')+1] + ['/usr/bin/python3','-I',str(Path(__file__).resolve()),'--inner',str(path)]
|
||||
diagnostics = open(data['bwrap_stderr'], 'wb')
|
||||
child = subprocess.Popen(args, env=env, stdout=subprocess.DEVNULL, stderr=diagnostics,
|
||||
preexec_fn=functools.partial(a.child_limits,1024**2,os.getpid()))
|
||||
try:
|
||||
temporary = Path(data['bwrap_pid'] + '.tmp')
|
||||
temporary.write_text(str(child.pid))
|
||||
temporary.replace(Path(data['bwrap_pid']))
|
||||
return child.wait(timeout=30)
|
||||
finally:
|
||||
if child.poll() is None:
|
||||
child.kill(); child.wait(timeout=5)
|
||||
diagnostics.close()
|
||||
|
||||
|
||||
def main():
|
||||
receipt={'status':'unverified','scope':'generated namespace hard-parent-loss process tree only',
|
||||
'real_index_opened':False,'real_credentials_read':False,'provider_calls':0,
|
||||
'host_scanner_modified':False}
|
||||
descriptors={}; child=None; fixture=None; inner_area=None
|
||||
try:
|
||||
if p.sha(p.BWRAP) != p.BWRAP_SHA:
|
||||
raise p.Refused('bwrap-drift')
|
||||
a=adapter()
|
||||
previous=set(Path('/dev/shm').glob('rmgr-secret-scan-*'))
|
||||
with a.private_storage() as (fixture, outer_watcher):
|
||||
data={'root':p.identity(Path('/')),'shm':p.identity(Path('/dev/shm')),
|
||||
'ns':p.namespaces(),'host_null':p.null_identity(),'readback':str(fixture/'inner.readback'),
|
||||
'bwrap_pid':str(fixture/'bwrap.pid'),
|
||||
'early_ready':str(fixture/'inner.early.ready'),
|
||||
'phase_readback':str(fixture/'inner.phase.json'),
|
||||
'bwrap_stderr':str(fixture/'bwrap.stderr'),
|
||||
'release_gate':str(fixture/'release.gate'), 'gate_token':os.urandom(16).hex(),
|
||||
'interpreter_sha256':p.sha(Path('/usr/bin/python3'))}
|
||||
path=fixture/'spec.json'; path.write_text(json.dumps(data))
|
||||
diagnostics = open(fixture/'spawner.stderr', 'wb')
|
||||
child=subprocess.Popen([sys.executable,'-I',str(Path(__file__).resolve()),'--spawn',str(path)],
|
||||
env={'PATH':'/usr/bin:/bin','HOME':'/home/worsch','LANG':'C'},
|
||||
stdout=subprocess.DEVNULL,stderr=diagnostics,
|
||||
preexec_fn=functools.partial(a.child_limits,1024**2,os.getpid()))
|
||||
try:
|
||||
descriptors['spawner']=os.pidfd_open(child.pid)
|
||||
# Wait for atomic pre-effect readiness, register both exact
|
||||
# process identities, then release only this generated gate.
|
||||
until=time.monotonic()+8
|
||||
early=Path(data['early_ready']); bwrap_pid=Path(data['bwrap_pid'])
|
||||
while (not early.exists() or not bwrap_pid.exists()) and child.poll() is None and time.monotonic()<until:
|
||||
time.sleep(.01)
|
||||
if not early.exists() or not bwrap_pid.exists():
|
||||
raise p.Refused('namespace-early-registration-failed')
|
||||
ready=json.loads(early.read_text())
|
||||
if set(ready) != {'inner_pid','namespace_identity_verified'} or not ready['namespace_identity_verified']:
|
||||
raise p.Refused('namespace-early-readiness-refused')
|
||||
descriptors['bwrap']=os.pidfd_open(int(bwrap_pid.read_text()))
|
||||
descriptors['inner']=os.pidfd_open(ready['inner_pid'])
|
||||
for descriptor in (descriptors['bwrap'],descriptors['inner']):
|
||||
dead,_,_=select.select([descriptor],[],[],0)
|
||||
if dead:
|
||||
raise p.Refused('namespace-creator-died-before-release')
|
||||
gate = Path(data['release_gate'])
|
||||
temporary_gate = gate.with_suffix('.tmp')
|
||||
temporary_gate.write_text(data['gate_token'])
|
||||
temporary_gate.replace(gate)
|
||||
until=time.monotonic()+8
|
||||
readback=Path(data['readback'])
|
||||
while not readback.exists() and child.poll() is None and time.monotonic()<until:
|
||||
time.sleep(.02)
|
||||
if not readback.exists():
|
||||
raise p.Refused('namespace-lifecycle-preflight-failed')
|
||||
body=json.loads(readback.read_text()); inner_area=Path(body['storage'])
|
||||
if (not body['namespace_identity_verified'] or p.identity(inner_area)!=body['storage_identity']
|
||||
or not str(inner_area).startswith('/dev/shm/rmgr-secret-scan-')):
|
||||
raise p.Refused('lifecycle-readback-refused')
|
||||
if body['inner_pid'] != ready['inner_pid']:
|
||||
raise p.Refused('namespace-inner-identity-changed')
|
||||
descriptors['cleanup_observer']=os.pidfd_open(body['watcher_pid'])
|
||||
# Exactly this new disposable spawner is killed. The calling tool,
|
||||
# shell, test harness, observer and their process groups are untouched.
|
||||
signal.pidfd_send_signal(descriptors['spawner'],signal.SIGKILL)
|
||||
child.wait(timeout=5)
|
||||
until=time.monotonic()+8
|
||||
pending=set(descriptors.values())
|
||||
while pending and time.monotonic()<until:
|
||||
ready,_,_=select.select(list(pending),[],[],max(0,until-time.monotonic()))
|
||||
pending.difference_update(ready)
|
||||
if pending or inner_area.exists():
|
||||
raise p.Refused('namespace-hard-loss-cleanup-unverified')
|
||||
receipt.update(namespace_identity_verified=True, pre_effect_pidfd_registration_verified=True,
|
||||
exact_disposable_parent_killed=True, null_device_readback=body['null_device_readback'],
|
||||
bwrap_inner_and_cleanup_observer_death_verified=True,inner_storage_removed=True)
|
||||
finally:
|
||||
# Quiesce named source/readback creators before the outer storage
|
||||
# context can remove their directory. Never kill the independent
|
||||
# cleanup observer; it owns the inner storage recovery duty.
|
||||
for name in ('spawner', 'bwrap', 'inner'):
|
||||
descriptor = descriptors.get(name)
|
||||
if descriptor is None:
|
||||
continue
|
||||
dead, _, _ = select.select([descriptor], [], [], 0)
|
||||
if not dead:
|
||||
try:
|
||||
signal.pidfd_send_signal(descriptor, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
# Retain the quiescence duty until exact death is proven.
|
||||
select.select([descriptor], [], [])
|
||||
if child.poll() is None:
|
||||
child.kill()
|
||||
child.wait(timeout=5)
|
||||
observer = descriptors.get('cleanup_observer')
|
||||
if observer is not None:
|
||||
select.select([observer], [], [], 8)
|
||||
diagnostics.close()
|
||||
phase_path=Path(data['phase_readback'])
|
||||
if phase_path.exists():
|
||||
receipt['last_inner_phase']=json.loads(phase_path.read_text())
|
||||
receipt['disposable_spawner_returncode']=child.returncode
|
||||
# This lifecycle fixture handles public sentinel bytes only, no
|
||||
# keys/indexes/source blobs. Retain bounded actual diagnostics.
|
||||
for name in ('bwrap','spawner'):
|
||||
diagnostic=fixture/(name+'.stderr')
|
||||
if diagnostic.exists():
|
||||
with diagnostic.open('rb') as output:
|
||||
content=output.read(8193)
|
||||
receipt[name+'_stderr_public']=content[:8192].decode('utf-8','replace')
|
||||
receipt[name+'_stderr_truncated']=len(content)>8192
|
||||
if fixture.exists() or set(Path('/dev/shm').glob('rmgr-secret-scan-*'))-previous:
|
||||
raise p.Refused('outer-storage-cleanup-unverified')
|
||||
receipt.update(status='verified',fixture_removed=True,new_scan_storage_absent=True)
|
||||
except Exception as error:
|
||||
receipt['reason']=error.args[0] if isinstance(error,p.Refused) else 'lifecycle-probe-unavailable'
|
||||
finally:
|
||||
if child is not None and child.poll() is None:
|
||||
try:
|
||||
signal.pidfd_send_signal(descriptors['spawner'],signal.SIGKILL)
|
||||
child.wait(timeout=5)
|
||||
except (OSError,subprocess.SubprocessError):
|
||||
pass
|
||||
for fd in descriptors.values():
|
||||
os.close(fd)
|
||||
receipt['fixture_removed']=fixture is None or not fixture.exists()
|
||||
receipt['inner_storage_removed']=inner_area is None or not inner_area.exists()
|
||||
receipt['driver_sha256']=p.sha(Path(__file__))
|
||||
(HERE/'namespace-lifecycle-results.json').write_text(json.dumps(receipt,indent=2)+'\n')
|
||||
print(json.dumps({'status':receipt['status'],'fixture_removed':receipt['fixture_removed'],
|
||||
'inner_storage_removed':receipt['inner_storage_removed']}))
|
||||
return 0 if receipt['status']=='verified' else 2
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
parser=argparse.ArgumentParser();parser.add_argument('--spawn',type=Path);parser.add_argument('--inner',type=Path)
|
||||
args=parser.parse_args()
|
||||
raise SystemExit(spawn(args.spawn) if args.spawn else inner(args.inner) if args.inner else main())
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
.......... [100%]
|
||||
10 passed in 0.06s
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
packet=/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/scanner-unavailable-r1
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
/home/worsch/repo-manager/.venv/bin/pytest -q "$packet/test_namespace_profile.py" > "$packet/profile-test-results.txt" 2>&1
|
||||
/home/worsch/repo-manager/.venv/bin/python "$packet/probe_namespace_lifecycle.py" > "$packet/lifecycle-driver-result.txt"
|
||||
/home/worsch/repo-manager/.venv/bin/python "$packet/probe_namespace_hooks.py" > "$packet/hook-driver-result.txt"
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
spec = importlib.util.spec_from_file_location('namespace_probe', Path(__file__).with_name('probe_namespace_hooks.py'))
|
||||
probe = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(probe)
|
||||
|
||||
|
||||
def profile(shadow=None):
|
||||
return probe.command(Path('/home/worsch/repo-manager'),
|
||||
{'PATH': '/usr/bin:/bin', 'GIT_INDEX_FILE': '/dev/shm/generated/index'},
|
||||
Path('/dev/shm/generated/spec.json'), shadow)
|
||||
|
||||
|
||||
def test_mount_profile_preserves_real_shm_and_readonly_root():
|
||||
args = profile()
|
||||
assert args[args.index('--ro-bind') + 1:args.index('--ro-bind') + 3] == ['/', '/']
|
||||
assert args[args.index('--bind') + 1:args.index('--bind') + 3] == ['/dev/shm', '/dev/shm']
|
||||
assert '--tmpfs' not in args
|
||||
|
||||
|
||||
def test_pid_namespace_preserved_for_independent_cleanup_observer():
|
||||
args = profile()
|
||||
assert '--unshare-user' in args and '--unshare-net' in args
|
||||
assert '--unshare-pid' not in args
|
||||
assert '--die-with-parent' in args
|
||||
|
||||
|
||||
def test_shadow_mount_targets_only_exact_admitted_scanner():
|
||||
args = profile(Path('/dev/shm/generated/empty-0400'))
|
||||
mounts = [args[i + 1:i + 3] for i, arg in enumerate(args) if arg == '--ro-bind']
|
||||
assert mounts == [['/', '/'], ['/dev/shm/generated/empty-0400', str(probe.BINARY)]]
|
||||
assert '--chmod' not in args
|
||||
|
||||
|
||||
def test_child_interpreter_and_driver_are_explicit():
|
||||
args = profile()
|
||||
assert args[args.index('--') + 1:args.index('--') + 3] == ['/usr/bin/python3', '-I']
|
||||
assert '--clearenv' in args
|
||||
|
||||
|
||||
def test_unknown_target_spec_refuses():
|
||||
spec = dict(root='/unreviewed', hook='/unreviewed/hook', shadow=True,
|
||||
readback='/dev/shm/rmgr-secret-scan-test/readback', host_root=[1, 2], host_shm=[3, 4],
|
||||
host_ns={}, host_null=[1,2,3,True], uid=1, gid=1, interpreter_sha256='x', driver_sha256='x')
|
||||
with pytest.raises(probe.Refused, match='spec-target-refused'):
|
||||
probe.check_spec(spec)
|
||||
|
||||
|
||||
def test_inside_refuses_without_actual_namespace_identity(tmp_path, monkeypatch):
|
||||
spec = dict(root='/home/worsch/repo-manager', hook='/home/worsch/repo-manager/.githooks/pre-commit',
|
||||
shadow=True, readback='/dev/shm/rmgr-secret-scan-test/readback', host_root=[0, 0],
|
||||
host_shm=[0, 0], host_ns=probe.namespaces(), host_null=[1,2,3,True], uid=0, gid=0,
|
||||
interpreter_sha256='x', driver_sha256='x')
|
||||
path = tmp_path/'spec.json'; path.write_text(json.dumps(spec))
|
||||
monkeypatch.setattr(probe.os, 'execve', lambda *args: pytest.fail('hook must not execute'))
|
||||
assert probe.inside(path) == 23
|
||||
|
||||
|
||||
def test_lifecycle_gate_refuses_storage_without_coordinator_release(tmp_path, monkeypatch):
|
||||
spec = importlib.util.spec_from_file_location('namespace_lifecycle', Path(__file__).with_name('probe_namespace_lifecycle.py'))
|
||||
life = importlib.util.module_from_spec(spec); spec.loader.exec_module(life)
|
||||
data = {'root':[1,2], 'shm':[1,2], 'ns':{'user':'old-user','mnt':'old-mnt','net':'old-net','pid':'same-pid'},
|
||||
'interpreter_sha256':'interpreter', 'host_null':[1,2,3,True], 'early_ready':str(tmp_path/'early'),
|
||||
'release_gate':str(tmp_path/'absent-gate'), 'gate_token':'exact-generated-gate'}
|
||||
path = tmp_path/'spec.json'; path.write_text(json.dumps(data))
|
||||
monkeypatch.setattr(life.p, 'identity', lambda _: [1,2])
|
||||
monkeypatch.setattr(life.p, 'namespaces', lambda: {'user':'new-user','mnt':'new-mnt','net':'new-net','pid':'same-pid'})
|
||||
monkeypatch.setattr(life.p, 'sha', lambda _: 'interpreter')
|
||||
monkeypatch.setattr(life.p, 'check_null', lambda _: {'read_write_open_verified':True})
|
||||
clock = [0]
|
||||
monkeypatch.setattr(life.time, 'monotonic', lambda: clock[0])
|
||||
monkeypatch.setattr(life.time, 'sleep', lambda _: clock.__setitem__(0, 9))
|
||||
monkeypatch.setattr(life, 'adapter', lambda: pytest.fail('source storage must wait for registered creator release'))
|
||||
assert life.inner(path) == 25
|
||||
assert json.loads((tmp_path/'early').read_text())['namespace_identity_verified'] is True
|
||||
|
||||
|
||||
def test_public_phase_never_copies_arbitrary_exception_message(tmp_path, monkeypatch):
|
||||
spec = importlib.util.spec_from_file_location('namespace_lifecycle_diagnostics', Path(__file__).with_name('probe_namespace_lifecycle.py'))
|
||||
life = importlib.util.module_from_spec(spec); spec.loader.exec_module(life)
|
||||
path=tmp_path/'spec.json'; phase=tmp_path/'phase.json'
|
||||
path.write_text(json.dumps({'phase_readback':str(phase)}))
|
||||
def fail(_):
|
||||
raise ValueError('unreviewed synthetic exception payload')
|
||||
monkeypatch.setattr(life, 'inner_body', fail)
|
||||
assert life.inner(path)==26
|
||||
recorded=phase.read_text()
|
||||
assert 'unreviewed synthetic exception payload' not in recorded
|
||||
assert json.loads(recorded)=={'phase':'inner-exception','last_successful_phase':'inner-started','exception_type':'ValueError','errno':None}
|
||||
|
||||
|
||||
def test_only_null_device_is_exposed_as_device_bind():
|
||||
args=profile()
|
||||
mounts=[args[i+1:i+3] for i,arg in enumerate(args) if arg=='--dev-bind']
|
||||
assert mounts==[['/dev/null','/dev/null']]
|
||||
|
||||
|
||||
def test_wrong_null_identity_refuses_before_open(monkeypatch):
|
||||
monkeypatch.setattr(probe,'null_identity',lambda:[1,2,3,True])
|
||||
monkeypatch.setattr(probe.os,'open',lambda *args: pytest.fail('mismatched device must not open'))
|
||||
with pytest.raises(probe.Refused,match='null-device-identity-refused'):
|
||||
probe.check_null([1,2,4,True])
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"owner": "secrets-engine",
|
||||
"exit_code": 0,
|
||||
"stdout": "[main c59bbf5] feat(recovery): prepare owner-bound offline continuity preflight\n 5 files changed, 248 insertions(+)\n create mode 100644 docs/keycape-isolated-recovery-selector.md\n create mode 100644 docs/keycape-recovery-selector.json\n create mode 100644 tests/test_keycape_recovery_selector.py\n create mode 100644 tools/check-keycape-recovery-selector.py\n create mode 100644 workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md\n",
|
||||
"stderr": "{\"blocked_blob_ids\": [], \"scanned_blobs\": 5, \"scanner_sha256\": \"88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509\", \"scanner_version\": \"8.30.1\", \"scope\": \"changed-staged-blobs\", \"status\": \"clean\"}\n",
|
||||
"head": "c59bbf568c0be8a72ac593508f48c9b1f61ec4c1",
|
||||
"files": {
|
||||
"tools/check-keycape-recovery-selector.py": "sha256:e3e5e3ee743a212247701d6cb40fcf5b7f19349126fa9b32cbef9a3cc9459150",
|
||||
"tests/test_keycape_recovery_selector.py": "sha256:221112ba303c58bc4205b577359ca90d8a6f60d09eb0b538990b1e53a156e262",
|
||||
"docs/keycape-isolated-recovery-selector.md": "sha256:ed23b0df3d4b49e4b76df795455153c805971ccd37886836c960f550709e0739",
|
||||
"docs/keycape-recovery-selector.json": "sha256:02077af3c18b24f5e4a85992a3d4d0df51a8e458de1e48b48f7eb5f482257012",
|
||||
"workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md": "sha256:b3cab4e8d14b3b03179fc72a6e9ce1c0ee840cfa3247c022c1324d60378bde5b"
|
||||
},
|
||||
"managed_identifier_assignments": {
|
||||
"schema": "repo-manager.identifier-assignment.v1",
|
||||
"ok": true,
|
||||
"executed": true,
|
||||
"repo": "secrets-engine",
|
||||
"namespace": "helixforge",
|
||||
"assignments": [
|
||||
{
|
||||
"kind": "workplan",
|
||||
"record_id": "SECRETS-WP-0015",
|
||||
"uuid": "68a0642e-4908-5260-89c9-3adce60e6a4d",
|
||||
"path": "workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md"
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"record_id": "SECRETS-WP-0015-T01",
|
||||
"uuid": "a3e0e243-edac-5ade-a3d5-f58dac41633a",
|
||||
"path": "workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md"
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"record_id": "SECRETS-WP-0015-T02",
|
||||
"uuid": "eab3b758-10ce-5464-b5f7-3a152ee1327a",
|
||||
"path": "workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md"
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"record_id": "SECRETS-WP-0015-T03",
|
||||
"uuid": "6262a915-7655-50be-b9f7-481f816d7335",
|
||||
"path": "workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md"
|
||||
},
|
||||
{
|
||||
"kind": "task",
|
||||
"record_id": "SECRETS-WP-0015-T04",
|
||||
"uuid": "aeec6eb0-39e2-5f7c-8f5d-778133edfc73",
|
||||
"path": "workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md"
|
||||
}
|
||||
],
|
||||
"historical_invalid_identifiers": [],
|
||||
"files_changed": [
|
||||
"workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"repo_slug": "secrets-engine",
|
||||
"repo_path": "/home/worsch/secrets-engine",
|
||||
"source_revision": "c59bbf568c0be8a72ac593508f48c9b1f61ec4c1",
|
||||
"source_revision_reason": null,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-33",
|
||||
"message": "WORK-RECORDS.md is missing or stale \u2014 regenerate via fix-consistency (CUST-WP-0061-T04 generated work-record index)",
|
||||
"file_path": "WORK-RECORDS.md",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": true
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-23",
|
||||
"message": "Lifecycle drift in 'secrets-wp-0013': workplan status 'proposed' has an active task \u2014 report only (ADR-007: do not auto-promote proposed)",
|
||||
"file_path": "workplans/SECRETS-WP-0013-task-scoped-openbao-execution.md",
|
||||
"db_id": "5e38aecc-85ca-5f55-94c6-9fbb08604819",
|
||||
"file_value": "proposed",
|
||||
"db_value": "active",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-06",
|
||||
"message": "Authoritative workplan 68a0642e\u2026 is absent from this projection",
|
||||
"file_path": "workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md",
|
||||
"db_id": "68a0642e-4908-5260-89c9-3adce60e6a4d",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": true
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Closed DB workstream 'secrets-wp-0002' (id=7929a2d1\u2026, status=finished) has no backing workplan file",
|
||||
"file_path": "",
|
||||
"db_id": "7929a2d1-3006-5d4b-85e4-c62da055ee9b",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-28",
|
||||
"message": "3 unread inbox message(s) older than 3 day(s): railiance-platform:0490edcb, 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 16/17",
|
||||
"C-17 cleared: pushed 1 backlogged commit(s)",
|
||||
"C-33 fixed: regenerated WORK-RECORDS.md",
|
||||
"C-06 fixed: created workstream 68a0642e\u2026 for SECRETS-WP-0015, wrote ID to SECRETS-WP-0015-original-keycape-recovery-selector.md",
|
||||
" + task SECRETS-WP-0015-T01 \u2192 a3e0e243\u2026",
|
||||
" + task SECRETS-WP-0015-T02 \u2192 eab3b758\u2026",
|
||||
" + task SECRETS-WP-0015-T03 \u2192 6262a915\u2026",
|
||||
" + task SECRETS-WP-0015-T04 \u2192 aeec6eb0\u2026",
|
||||
"brief: .custodian-brief.md updated",
|
||||
"push: pushed"
|
||||
],
|
||||
"summary": {
|
||||
"fail": 0,
|
||||
"automation_error": 0,
|
||||
"warn": 5,
|
||||
"info": 1
|
||||
},
|
||||
"result": "warn"
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
WARN: git commit failed for writeback: Generated-only commits rejected: staged
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
||||
# Custodian Brief — secrets-engine
|
||||
|
||||
**Domain:** infotech
|
||||
**Last synced:** 2026-10-16 19:07 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Active Workstreams
|
||||
|
||||
### Design and prove task-scoped protected OpenBao execution
|
||||
Progress: 1/3 done | workplan_id: `5e38aecc-85ca-5f55-94c6-9fbb08604819`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Define the exact mediated execution contract `b2f0d462`
|
||||
*(wait: Independent Platform mediated-contract review and identity/approval/PDP owner returns remain required; D3 wrapped primitive source is implemented, no native entitlement is inferred.)*
|
||||
- ! Implement and prove one native metadata action `5f8f7c41`
|
||||
|
||||
### Constrain PDP transport and authenticate decision responses
|
||||
Progress: 1/3 done | workplan_id: `7252469d-d7a5-5c18-8911-b511b8c6a736`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Verify the admitted native consumer and rotation path `85aa4c78`
|
||||
*(wait: Requires reviewed verification-key admission, source enforcement and exact native rollout receipt; no production trust change is proposed yet.)*
|
||||
- ► Implement independently anchored responder authentication `877782e5`
|
||||
|
||||
### Activate native Claude credential delivery for Glas
|
||||
Progress: 2/3 done | workplan_id: `40ccc3b4-d046-5a58-8649-e7935f45c974`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Activate the approved native lane and verify real owner delivery `f8069c8a`
|
||||
*(wait: Next owner action: reconcile held parent reservation 6efa9436-6a91-47c0-94e5-b31b5a0e7e3a (EUR 10, observed USD still null) from existing provider records. No corrected runtime digest is admitted; do not retry or replay consumed approvals.)*
|
||||
|
||||
### Adopt concrete OpenBao credential lanes from ops-warden
|
||||
Progress: 4/6 done | workplan_id: `31f7f8ea-7f73-516c-8877-f03a13f1db82`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Stage live apply and verification `fb103f1e`
|
||||
*(wait: Owner work first: publish a reviewed scoped-apply command per lane. Per-lane approvals are no longer needed (D1 standing rule); each lane with a command enters the attended window.)*
|
||||
- ! Reconcile routing ownership and retire interim proxies `1431edae`
|
||||
*(wait: Needs owner-agreed routing/proxy retirement for each verified lane and custody disposition of the legacy npm pointer; a single approved OpenRouter key-check does not authorize broader routing cutover.)*
|
||||
|
||||
### Evolve the Lifecycle engine to the accepted security layer model
|
||||
Progress: 5/6 done | workplan_id: `9c9e5164-b2f5-5ea2-a557-5368d65e9fe0`
|
||||
|
||||
**Open tasks:**
|
||||
- ! No standing engine credential `d7bc8bdc`
|
||||
*(wait: Published JWT contract and positive login/revocation are delivered. Native KEY-WP-0039-T02 claim negatives, CCR-2026-0038 effective-policy/capability/expiry/use checks and MASON-WP-0009-T05 remain. Login-self-only grants no protected-operation authority: agree and implement separately reviewed task-scoped backend execution entitlement, then prove functional steady-state service-identity adoption without bootstrap/env fallback.)*
|
||||
|
||||
### Production-safe provisioning, authorization, and lifecycle hardening
|
||||
Progress: 6/7 done | workplan_id: `68a39be1-bd9c-5133-ad64-e7bca892aaf3`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Resume native production lane adoption `a0a1dd92`
|
||||
*(wait: Engine approval hardening is complete. Remaining per-lane cutover includes native routing/proxy retirement with ops-warden under SECRETS-WP-0006-T05/T06; the exact OpenRouter key-check receipt does not establish broader recipient readiness.)*
|
||||
|
||||
## Inbox Hygiene
|
||||
|
||||
**Stale unread:** 3 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.
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# Work Records — secrets-engine
|
||||
|
||||
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
|
||||
> stage 3). Do not edit by hand — edit the source file/block listed for
|
||||
> each record and re-run fix-consistency to refresh this index. Archived
|
||||
> workplans are omitted; closed decisions/intakes/engagements stay listed
|
||||
> so recently-resolved work is still visible. [auto]
|
||||
|
||||
| Kind | ID | Status | Lane | Source |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| workplan | SECRETS-WP-ADHOC-2026-08-21 | finished | — | workplans/ADHOC-2026-08-21.md |
|
||||
| workplan | SECRETS-WP-ADHOC-2026-08-23 | finished | — | workplans/ADHOC-2026-08-23.md |
|
||||
| workplan | SECRETS-WP-0001 | finished | — | workplans/SECRETS-WP-0001-statehub-bootstrap.md |
|
||||
| workplan | SECRETS-WP-0002 | finished | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| workplan | SECRETS-WP-0004 | finished | — | workplans/SECRETS-WP-0004-warden-sign-token-lane.md |
|
||||
| workplan | SECRETS-WP-0005 | finished | — | workplans/SECRETS-WP-0005-scope-intent-value-gaps.md |
|
||||
| workplan | SECRETS-WP-0006 | blocked | — | workplans/SECRETS-WP-0006-catalog-lane-adoption.md |
|
||||
| workplan | SECRETS-WP-0007 | blocked | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md |
|
||||
| workplan | SECRETS-WP-0008 | blocked | — | workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md |
|
||||
| workplan | SECRETS-WP-0009 | blocked | — | workplans/SECRETS-WP-0009-glas-claude-native-delivery.md |
|
||||
| workplan | SECRETS-WP-0010 | finished | — | workplans/SECRETS-WP-0010-openrouter-native-access.md |
|
||||
| workplan | SECRETS-WP-0011 | finished | — | workplans/SECRETS-WP-0011-multi-lane-exec-owner-delivery.md |
|
||||
| workplan | SECRETS-WP-0012 | finished | — | workplans/SECRETS-WP-0012-keycape-openbao-consumer-custody.md |
|
||||
| workplan | SECRETS-WP-0013 | proposed | — | workplans/SECRETS-WP-0013-task-scoped-openbao-execution.md |
|
||||
| workplan | SECRETS-WP-0014 | active | — | workplans/SECRETS-WP-0014-pdp-transport-and-responder-authentication.md |
|
||||
| task | SECRETS-WP-ADHOC-2026-08-21-T01 | done | — | workplans/ADHOC-2026-08-21.md |
|
||||
| task | SECRETS-WP-ADHOC-2026-08-23-T01 | done | — | workplans/ADHOC-2026-08-23.md |
|
||||
| task | SECRETS-WP-0001-T01 | done | — | workplans/SECRETS-WP-0001-statehub-bootstrap.md |
|
||||
| task | SECRETS-WP-0001-T02 | done | — | workplans/SECRETS-WP-0001-statehub-bootstrap.md |
|
||||
| task | SECRETS-WP-0001-T03 | done | — | workplans/SECRETS-WP-0001-statehub-bootstrap.md |
|
||||
| task | SECRETS-WP-0002-T01 | done | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| task | SECRETS-WP-0002-T02 | done | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| task | SECRETS-WP-0002-T03 | done | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| task | SECRETS-WP-0002-T04 | done | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| task | SECRETS-WP-0002-T05 | done | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| task | SECRETS-WP-0002-T06 | done | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| task | SECRETS-WP-0002-T07 | done | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| task | SECRETS-WP-0002-T08 | done | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| task | SECRETS-WP-0002-T09 | done | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| task | SECRETS-WP-0002-T10 | done | — | workplans/SECRETS-WP-0002-bootstrap.md |
|
||||
| task | SECRETS-WP-0004-T01 | done | — | workplans/SECRETS-WP-0004-warden-sign-token-lane.md |
|
||||
| task | SECRETS-WP-0004-T02 | done | — | workplans/SECRETS-WP-0004-warden-sign-token-lane.md |
|
||||
| task | SECRETS-WP-0004-T03 | done | — | workplans/SECRETS-WP-0004-warden-sign-token-lane.md |
|
||||
| task | SECRETS-WP-0004-T04 | done | — | workplans/SECRETS-WP-0004-warden-sign-token-lane.md |
|
||||
| task | SECRETS-WP-0004-T05 | done | — | workplans/SECRETS-WP-0004-warden-sign-token-lane.md |
|
||||
| task | SECRETS-WP-0005-T01 | done | — | workplans/SECRETS-WP-0005-scope-intent-value-gaps.md |
|
||||
| task | SECRETS-WP-0005-T02 | done | — | workplans/SECRETS-WP-0005-scope-intent-value-gaps.md |
|
||||
| task | SECRETS-WP-0005-T03 | done | — | workplans/SECRETS-WP-0005-scope-intent-value-gaps.md |
|
||||
| task | SECRETS-WP-0005-T04 | done | — | workplans/SECRETS-WP-0005-scope-intent-value-gaps.md |
|
||||
| task | SECRETS-WP-0005-T05 | done | — | workplans/SECRETS-WP-0005-scope-intent-value-gaps.md |
|
||||
| task | SECRETS-WP-0005-T06 | done | — | workplans/SECRETS-WP-0005-scope-intent-value-gaps.md |
|
||||
| task | SECRETS-WP-0005-T07 | done | — | workplans/SECRETS-WP-0005-scope-intent-value-gaps.md |
|
||||
| task | SECRETS-WP-0006-T01 | done | — | workplans/SECRETS-WP-0006-catalog-lane-adoption.md |
|
||||
| task | SECRETS-WP-0006-T02 | done | — | workplans/SECRETS-WP-0006-catalog-lane-adoption.md |
|
||||
| task | SECRETS-WP-0006-T03 | done | — | workplans/SECRETS-WP-0006-catalog-lane-adoption.md |
|
||||
| task | SECRETS-WP-0006-T04 | done | — | workplans/SECRETS-WP-0006-catalog-lane-adoption.md |
|
||||
| task | SECRETS-WP-0006-T05 | wait | — | workplans/SECRETS-WP-0006-catalog-lane-adoption.md |
|
||||
| task | SECRETS-WP-0006-T06 | wait | — | workplans/SECRETS-WP-0006-catalog-lane-adoption.md |
|
||||
| task | SECRETS-WP-0007-T01 | done | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md |
|
||||
| task | SECRETS-WP-0007-T02 | done | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md |
|
||||
| task | SECRETS-WP-0007-T03 | done | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md |
|
||||
| task | SECRETS-WP-0007-T04 | done | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md |
|
||||
| task | SECRETS-WP-0007-T05 | done | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md |
|
||||
| task | SECRETS-WP-0007-T06 | done | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md |
|
||||
| task | SECRETS-WP-0007-T07 | wait | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md |
|
||||
| task | SECRETS-WP-0008-T01 | done | — | workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md |
|
||||
| task | SECRETS-WP-0008-T02 | done | — | workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md |
|
||||
| task | SECRETS-WP-0008-T03 | done | — | workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md |
|
||||
| task | SECRETS-WP-0008-T04 | done | — | workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md |
|
||||
| task | SECRETS-WP-0008-T05 | done | — | workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md |
|
||||
| task | SECRETS-WP-0008-T06 | wait | — | workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md |
|
||||
| task | SECRETS-WP-0009-T01 | done | — | workplans/SECRETS-WP-0009-glas-claude-native-delivery.md |
|
||||
| task | SECRETS-WP-0009-T02 | done | — | workplans/SECRETS-WP-0009-glas-claude-native-delivery.md |
|
||||
| task | SECRETS-WP-0009-T03 | wait | — | workplans/SECRETS-WP-0009-glas-claude-native-delivery.md |
|
||||
| task | SECRETS-WP-0010-T01 | done | — | workplans/SECRETS-WP-0010-openrouter-native-access.md |
|
||||
| task | SECRETS-WP-0010-T02 | done | — | workplans/SECRETS-WP-0010-openrouter-native-access.md |
|
||||
| task | SECRETS-WP-0010-T03 | done | — | workplans/SECRETS-WP-0010-openrouter-native-access.md |
|
||||
| task | SECRETS-WP-0011-T01 | done | — | workplans/SECRETS-WP-0011-multi-lane-exec-owner-delivery.md |
|
||||
| task | SECRETS-WP-0011-T02 | done | — | workplans/SECRETS-WP-0011-multi-lane-exec-owner-delivery.md |
|
||||
| task | SECRETS-WP-0011-T03 | done | — | workplans/SECRETS-WP-0011-multi-lane-exec-owner-delivery.md |
|
||||
| task | SECRETS-WP-0011-T04 | done | — | workplans/SECRETS-WP-0011-multi-lane-exec-owner-delivery.md |
|
||||
| task | SECRETS-WP-0011-T05 | done | — | workplans/SECRETS-WP-0011-multi-lane-exec-owner-delivery.md |
|
||||
| task | SECRETS-WP-0012-T01 | done | — | workplans/SECRETS-WP-0012-keycape-openbao-consumer-custody.md |
|
||||
| task | SECRETS-WP-0012-T02 | cancel | — | workplans/SECRETS-WP-0012-keycape-openbao-consumer-custody.md |
|
||||
| task | SECRETS-WP-0012-T03 | done | — | workplans/SECRETS-WP-0012-keycape-openbao-consumer-custody.md |
|
||||
| task | SECRETS-WP-0012-T04 | done | — | workplans/SECRETS-WP-0012-keycape-openbao-consumer-custody.md |
|
||||
| task | SECRETS-WP-0013-T01 | wait | — | workplans/SECRETS-WP-0013-task-scoped-openbao-execution.md |
|
||||
| task | SECRETS-WP-0013-T02 | done | — | workplans/SECRETS-WP-0013-task-scoped-openbao-execution.md |
|
||||
| task | SECRETS-WP-0013-T03 | wait | — | workplans/SECRETS-WP-0013-task-scoped-openbao-execution.md |
|
||||
| task | SECRETS-WP-0014-T01 | done | — | workplans/SECRETS-WP-0014-pdp-transport-and-responder-authentication.md |
|
||||
| task | SECRETS-WP-0014-T02 | progress | — | workplans/SECRETS-WP-0014-pdp-transport-and-responder-authentication.md |
|
||||
| task | SECRETS-WP-0014-T03 | wait | — | workplans/SECRETS-WP-0014-pdp-transport-and-responder-authentication.md |
|
||||
| intake | SECRETS-IN-0001 | closed | — | intakes/intakes.md |
|
||||
| intake | SECRETS-IN-0002 | closed | — | intakes/intakes.md |
|
||||
| intake | SECRETS-IN-0003 | closed | — | intakes/intakes.md |
|
||||
|
|
@ -0,0 +1 @@
|
|||
{".custodian-brief.md": "100644 f5952182bbfde84b60c85c4155da8c65f12ba0ab 0\t.custodian-brief.md\n", "WORK-RECORDS.md": "100644 70e5831d75b067c0d05efa3fab37a76a029cf9e9 0\tWORK-RECORDS.md\n"}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"owner": "secrets-engine",
|
||||
"base_revision": "535681052cd55ca613706ecc60c39d299929b3c5",
|
||||
"applied": true,
|
||||
"files": {
|
||||
"tools/check-keycape-recovery-selector.py": "sha256:e3e5e3ee743a212247701d6cb40fcf5b7f19349126fa9b32cbef9a3cc9459150",
|
||||
"tests/test_keycape_recovery_selector.py": "sha256:221112ba303c58bc4205b577359ca90d8a6f60d09eb0b538990b1e53a156e262",
|
||||
"docs/keycape-isolated-recovery-selector.md": "sha256:ed23b0df3d4b49e4b76df795455153c805971ccd37886836c960f550709e0739",
|
||||
"docs/keycape-recovery-selector.json": "sha256:02077af3c18b24f5e4a85992a3d4d0df51a8e458de1e48b48f7eb5f482257012",
|
||||
"workplans/SECRETS-WP-0015-original-keycape-recovery-selector.md": "sha256:22b148f0f403b8a9d471f34b2fae6938066123203d5c6f21ad463c5c262705c5"
|
||||
},
|
||||
"preexisting_brief_preserved": true,
|
||||
"native_acceptance": false
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"owner_base": "6cb43c0cc55212363a5406b02db046e2384a7c40",
|
||||
"files": [
|
||||
{
|
||||
"path": "tests/test_sops_private.py",
|
||||
"before_sha256": "sha256:b29c3759aacf7ab31b8a7909fe947b05b0ae5fe2acea88a5dac7e996101cb354",
|
||||
"after_sha256": "sha256:a5ff8d7847cdce151be44cdf4a89385d5e2585cdd59143dee4be3cc6106eb7f5"
|
||||
},
|
||||
{
|
||||
"path": "tests/test_sops_write.py",
|
||||
"before_sha256": "sha256:3d4b26080c9a6328caaf82072363d40d27b2c96b07c441c7922c164f29fcfbd9",
|
||||
"after_sha256": "sha256:f832925283e79589b3ce0bded7e50f49bfd4e78620c224549bb7d31a7868fca6"
|
||||
}
|
||||
],
|
||||
"patch_sha256": "sha256:1ee771f243408c1d4f7472bd59af89e8cd73e4aec47bc25471269b804ce22888",
|
||||
"real_sops_generated_crypto_tests": 65,
|
||||
"new_end_to_end_fd_cases": 4,
|
||||
"all_previously_skipped_real_sops_cases_now_run": 61,
|
||||
"production_runtime_admitted": false
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
--- a/tests/test_sops_private.py
|
||||
+++ b/tests/test_sops_private.py
|
||||
@@ -10,6 +10,40 @@
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOPS = shutil.which('sops')
|
||||
pytestmark = pytest.mark.skipif(SOPS is None, reason='SOPS required for real crypto regression')
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize('wrong_recipient', [False, True])
|
||||
+def test_real_sops_decrypt_with_only_sealed_descriptor(case, wrong_recipient):
|
||||
+ import fcntl
|
||||
+ original = case[1].read_bytes()
|
||||
+ material = (subprocess.run(['age-keygen'], capture_output=True, check=True).stdout
|
||||
+ if wrong_recipient else original)
|
||||
+ fd = os.memfd_create('generated-sops-custody', os.MFD_ALLOW_SEALING)
|
||||
+ try:
|
||||
+ os.fchmod(fd, 0o600); os.write(fd, material); os.lseek(fd, 0, os.SEEK_SET)
|
||||
+ fcntl.fcntl(fd, fcntl.F_ADD_SEALS,
|
||||
+ fcntl.F_SEAL_WRITE | fcntl.F_SEAL_GROW | fcntl.F_SEAL_SHRINK | fcntl.F_SEAL_SEAL)
|
||||
+ case[1].unlink()
|
||||
+ env = {**case[2], 'NETKINGDOM_CUSTODY_AGE_KEY_FD': str(fd)}
|
||||
+ env.pop('NETKINGDOM_CUSTODY_AGE_KEY_FILE')
|
||||
+ if wrong_recipient:
|
||||
+ # A correct ambient key must never rescue the wrong explicit FD.
|
||||
+ env['SOPS_AGE_KEY'] = original.decode()
|
||||
+ result = subprocess.run(['make', '-s', 'sops-decrypt', 'FILE=' + str(case[3]),
|
||||
+ 'OUTPUT=' + str(case[4])], cwd=case[0], env=env,
|
||||
+ pass_fds=(fd,), capture_output=True, text=True, timeout=15)
|
||||
+ assert not case[1].exists()
|
||||
+ assert 'synthetic-secret-payload' not in result.stdout + result.stderr
|
||||
+ if wrong_recipient:
|
||||
+ assert result.returncode != 0
|
||||
+ assert not case[4].exists()
|
||||
+ else:
|
||||
+ assert result.returncode == 0
|
||||
+ assert json.loads(case[4].read_text()) == {'credential': 'synthetic-secret-payload'}
|
||||
+ assert case[4].stat().st_mode & 0o077 == 0
|
||||
+ assert not list(case[4].parent.glob('.netkingdom-sops-*'))
|
||||
+ finally:
|
||||
+ os.close(fd)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
--- a/tests/test_sops_write.py
|
||||
+++ b/tests/test_sops_write.py
|
||||
@@ -11,6 +11,36 @@
|
||||
SOPS=shutil.which('sops')
|
||||
pytestmark=pytest.mark.skipif(SOPS is None,reason='real SOPS required')
|
||||
PLAIN='synthetic-sops-write-value'
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize('action', ['rotate', 'edit'])
|
||||
+def test_real_sops_write_with_only_sealed_descriptor(case, action):
|
||||
+ import fcntl
|
||||
+ fd = os.memfd_create('generated-sops-write-custody', os.MFD_ALLOW_SEALING)
|
||||
+ try:
|
||||
+ os.fchmod(fd, 0o600); os.write(fd, case['key'].read_bytes()); os.lseek(fd, 0, os.SEEK_SET)
|
||||
+ fcntl.fcntl(fd, fcntl.F_ADD_SEALS,
|
||||
+ fcntl.F_SEAL_WRITE | fcntl.F_SEAL_GROW | fcntl.F_SEAL_SHRINK | fcntl.F_SEAL_SEAL)
|
||||
+ case['key'].unlink()
|
||||
+ env = {**case['env'], 'NETKINGDOM_CUSTODY_AGE_KEY_FD': str(fd)}
|
||||
+ env.pop('NETKINGDOM_CUSTODY_AGE_KEY_FILE'); env.pop('SOPS_AGE_KEY_FILE')
|
||||
+ before = case['cipher'].read_bytes()
|
||||
+ result = subprocess.run(['make', '-s', 'sops-' + action,
|
||||
+ 'FILE=' + str(case['cipher']), 'INPUT=' + str(case['input'])],
|
||||
+ cwd=case['repo'], env=env, pass_fds=(fd,),
|
||||
+ capture_output=True, text=True, timeout=15)
|
||||
+ assert result.returncode == 0
|
||||
+ assert PLAIN not in result.stdout + result.stderr
|
||||
+ assert not case['key'].exists()
|
||||
+ assert before != case['cipher'].read_bytes()
|
||||
+ clear = subprocess.run([SOPS, '--decrypt', str(case['cipher'])],
|
||||
+ env={**env, 'SOPS_AGE_KEY_FILE': '/proc/self/fd/' + str(fd)},
|
||||
+ pass_fds=(fd,), capture_output=True, check=True)
|
||||
+ assert json.loads(clear.stdout) == {'credential': PLAIN + ('-edited' if action == 'edit' else '')}
|
||||
+ assert case['cipher'].stat().st_mode & 0o077 == 0
|
||||
+ assert not list(case['cipher'].parent.glob('.netkingdom-sops-write-*'))
|
||||
+ finally:
|
||||
+ os.close(fd)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"owner": "net-kingdom",
|
||||
"base_revision": "6cb43c0cc55212363a5406b02db046e2384a7c40",
|
||||
"applied": true,
|
||||
"files": {
|
||||
"tests/test_sops_private.py": "sha256:a5ff8d7847cdce151be44cdf4a89385d5e2585cdd59143dee4be3cc6106eb7f5",
|
||||
"tests/test_sops_write.py": "sha256:f832925283e79589b3ce0bded7e50f49bfd4e78620c224549bb7d31a7868fca6"
|
||||
},
|
||||
"preexisting_brief_preserved": true,
|
||||
"native_acceptance": false
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"owner": "net-kingdom",
|
||||
"base_revision": "6cb43c0cc55212363a5406b02db046e2384a7c40",
|
||||
"patch_sha256": "5364b1966334869c71a58ff2d90194db45c7ea6f3e60d57327a231f558437641",
|
||||
"files": {
|
||||
"workplans/NK-WP-0049-bootstrap-secret-free-recovery-handoff.md": "95dfb5a264ba179aba4aeddb602039a098af5c33c893a509d31f3631d39b7046"
|
||||
},
|
||||
"index_unchanged": true,
|
||||
"production_mutation": false,
|
||||
"registrar_enabled": false,
|
||||
"status": "applied_source_only"
|
||||
}
|
||||
|
|
@ -0,0 +1,858 @@
|
|||
[
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/key39-infra-finalization-r1/finalization-recheck-progress.json",
|
||||
"bytes": 973
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/key39-infra-finalization-r1/finalization-recheck.json",
|
||||
"bytes": 6768
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r1/apply_prepare.py",
|
||||
"bytes": 5464
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r1/candidate.patch",
|
||||
"bytes": 10999
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r1/preparation.json",
|
||||
"bytes": 3554
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r1/publication-inputs.json",
|
||||
"bytes": 16672
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r1/run_validation.py",
|
||||
"bytes": 4070
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r1/task-T35.md",
|
||||
"bytes": 2187
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r1/validation-child.py",
|
||||
"bytes": 2119
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r1/validation-guard.py",
|
||||
"bytes": 1737
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r1/workplan.candidate.md",
|
||||
"bytes": 95774
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r1/workplan.preimage.md",
|
||||
"bytes": 93587
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/apply_prepare.py",
|
||||
"bytes": 5464
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/candidate.patch",
|
||||
"bytes": 10999
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/canonical-validation.json",
|
||||
"bytes": 637
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/identifier-assignment.json",
|
||||
"bytes": 509
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/identifier-preview.json",
|
||||
"bytes": 510
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/preparation.json",
|
||||
"bytes": 3554
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/publication-inputs.allocated.json",
|
||||
"bytes": 841
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/publication-inputs.json",
|
||||
"bytes": 16672
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/revision-refresh.json",
|
||||
"bytes": 535
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/run_validation.py",
|
||||
"bytes": 4070
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/task-T35.md",
|
||||
"bytes": 2187
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/validation-child.py",
|
||||
"bytes": 2119
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/validation-guard.py",
|
||||
"bytes": 1737
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/workplan.allocated.md",
|
||||
"bytes": 95832
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/workplan.candidate.md",
|
||||
"bytes": 95774
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-publication-r2/workplan.preimage.md",
|
||||
"bytes": 93587
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-update-r1/candidate.patch",
|
||||
"bytes": 10999
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-update-r1/edit.py",
|
||||
"bytes": 5370
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-update-r1/implementation-r1.json",
|
||||
"bytes": 6352
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-update-r1/run.py",
|
||||
"bytes": 676
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-catalog-fixture-update-r1/test_runner.py",
|
||||
"bytes": 1279
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/canonical-validation.json",
|
||||
"bytes": 637
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/consistency.json",
|
||||
"bytes": 13229
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/consistency.stderr.txt",
|
||||
"bytes": 79
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/projection.json",
|
||||
"bytes": 36692
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/projection.stderr.txt",
|
||||
"bytes": 0
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/publication-commit.json",
|
||||
"bytes": 1450
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/publication-inputs.final.json",
|
||||
"bytes": 1023
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/publication-inputs.json",
|
||||
"bytes": 16672
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/run_validation.py",
|
||||
"bytes": 4070
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/validation-child.py",
|
||||
"bytes": 2119
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/validation-guard.py",
|
||||
"bytes": 1737
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/validation-inputs-refresh.json",
|
||||
"bytes": 572
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-current-source-publication-r3/verified-task-bindings.json",
|
||||
"bytes": 760
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-issuer-reference-integration-r1/apply.py",
|
||||
"bytes": 5250
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-issuer-reference-integration-r1/composition-regenerated.json",
|
||||
"bytes": 9331
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-issuer-reference-integration-r1/initial-syntax-failure.py.txt",
|
||||
"bytes": 6484
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-issuer-reference-integration-r1/type.preimage.yaml",
|
||||
"bytes": 24322
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-issuer-reference-integration-r1/verification.json",
|
||||
"bytes": 538
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-issuer-reference-integration-r1/workplan.preimage.md",
|
||||
"bytes": 95832
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/README.md",
|
||||
"bytes": 2023
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/canonical-application.json",
|
||||
"bytes": 364
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/canonical-net-kingdom-status-after.txt",
|
||||
"bytes": 333
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/canonical-validation.json",
|
||||
"bytes": 506
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/implementation.json",
|
||||
"bytes": 4277
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/manifest.json",
|
||||
"bytes": 1453
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/preimage-frozen.txt",
|
||||
"bytes": 87
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/preimage-mutant.txt",
|
||||
"bytes": 87
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/reproduce-scratch-checks.py",
|
||||
"bytes": 2371
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/scratch-kernel-mutation.patch",
|
||||
"bytes": 489
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/scratch.json",
|
||||
"bytes": 238
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/successor-frozen.txt",
|
||||
"bytes": 87
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/successor-mutant.txt",
|
||||
"bytes": 1779
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/test-admit-1-3-agent-mint-coverage.patch",
|
||||
"bytes": 4158
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/test-results.json",
|
||||
"bytes": 2403
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/test_admit_1_3_type.after.py",
|
||||
"bytes": 8901
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f1-implementation-r1/test_admit_1_3_type.before.py",
|
||||
"bytes": 7816
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/OWNER-REVIEW.md",
|
||||
"bytes": 4737
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/canonical-source-readonly-state.json",
|
||||
"bytes": 789
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/focused-tests.json",
|
||||
"bytes": 336
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/focused-tests.txt",
|
||||
"bytes": 1022
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/manifest.json",
|
||||
"bytes": 1671
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/netkingdom-entry-point.conditional.patch",
|
||||
"bytes": 1269
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/platform-candidate/docs/keycape-issuer-eso-contract.md",
|
||||
"bytes": 4616
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/platform-candidate/manifests/templates/keycape-issuer-eso.yaml",
|
||||
"bytes": 1315
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/platform-candidate/scripts/keycape_issuer_eso.py",
|
||||
"bytes": 3138
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/platform-candidate/tests/test_keycape_issuer_eso.py",
|
||||
"bytes": 6757
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/platform.candidate.patch",
|
||||
"bytes": 16431
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/review.json",
|
||||
"bytes": 3125
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/source-pins.json",
|
||||
"bytes": 3123
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/structural-and-patch-checks.json",
|
||||
"bytes": 281
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/type.before.yaml",
|
||||
"bytes": 24322
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-implementation-r1/type.candidate.yaml",
|
||||
"bytes": 24585
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/README.md",
|
||||
"bytes": 2644
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/canonical-validation.json",
|
||||
"bytes": 331
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/consistency.json",
|
||||
"bytes": 24932
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/consistency.stderr.txt",
|
||||
"bytes": 0
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/integrate_platform.py",
|
||||
"bytes": 4127
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/integration-checks.json",
|
||||
"bytes": 2523
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/manifest.json",
|
||||
"bytes": 1512
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/netkingdom-entry-point.conditional.patch",
|
||||
"bytes": 1269
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/packet-hashes.json",
|
||||
"bytes": 1073
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/platform-source/docs/keycape-issuer-eso-contract.md",
|
||||
"bytes": 4616
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/platform-source/manifests/templates/keycape-issuer-eso.yaml",
|
||||
"bytes": 1315
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/platform-source/scripts/keycape_issuer_eso.py",
|
||||
"bytes": 3138
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/platform-source/tests/test_keycape_issuer_eso.py",
|
||||
"bytes": 6757
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/progress-paragraph.md",
|
||||
"bytes": 1360
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/projection.json",
|
||||
"bytes": 14864
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/projection.stderr.txt",
|
||||
"bytes": 0
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/publication-commit.json",
|
||||
"bytes": 1352
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/publication-inputs.json",
|
||||
"bytes": 791
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-review-r1/REVIEW.md",
|
||||
"bytes": 4743
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-review-r1/evidence.json",
|
||||
"bytes": 2495
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-review-r1/focused-tests.txt",
|
||||
"bytes": 1150
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-review-r1/reproduce.py",
|
||||
"bytes": 4588
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-review-r1/review.json",
|
||||
"bytes": 1478
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/README.md",
|
||||
"bytes": 3793
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/candidate-validation.json",
|
||||
"bytes": 723
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/failure_inverse_omitted.txt",
|
||||
"bytes": 2257
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/focused-tests-before-admission-binding.json",
|
||||
"bytes": 894
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/focused-tests-before-admission-binding.txt",
|
||||
"bytes": 106
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/focused-tests.json",
|
||||
"bytes": 681
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/focused-tests.txt",
|
||||
"bytes": 101
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/implementation.json",
|
||||
"bytes": 6080
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/manifest.json",
|
||||
"bytes": 1933
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/negative-controls.json",
|
||||
"bytes": 1099
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/net-kingdom-unchanged-owner-status.txt",
|
||||
"bytes": 333
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/owner-contract-candidate.md",
|
||||
"bytes": 8157
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/owner_configuration_contract.py",
|
||||
"bytes": 3675
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/post_revision_guard_removed.txt",
|
||||
"bytes": 1085
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/railiance-apps-unchanged-owner-status.txt",
|
||||
"bytes": 23
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/test_owner_configuration_contract.py",
|
||||
"bytes": 16794
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/type-owner-configuration-inverse.candidate.patch",
|
||||
"bytes": 4508
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/type.before.yaml",
|
||||
"bytes": 24322
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r1/type.candidate.yaml",
|
||||
"bytes": 25909
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/README.md",
|
||||
"bytes": 3100
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/candidate-validation.json",
|
||||
"bytes": 761
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/focused-tests.json",
|
||||
"bytes": 715
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/focused-tests.txt",
|
||||
"bytes": 86
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/fresh-inverse-session.candidate.patch",
|
||||
"bytes": 918
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/fresh-session-guard-omitted.txt",
|
||||
"bytes": 27254
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/implementation.json",
|
||||
"bytes": 6392
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/manifest.json",
|
||||
"bytes": 2298
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/negative-controls.json",
|
||||
"bytes": 547
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/owner-backend-next.md",
|
||||
"bytes": 6278
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/owner-contract-candidate.md",
|
||||
"bytes": 9431
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/owner-source-inventory.json",
|
||||
"bytes": 2016
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/owner_configuration_contract.py",
|
||||
"bytes": 4122
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/test_owner_configuration_contract.py",
|
||||
"bytes": 22919
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/type-owner-configuration-inverse.candidate.patch",
|
||||
"bytes": 5004
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/type.before.yaml",
|
||||
"bytes": 24322
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-implementation-r2/type.candidate.yaml",
|
||||
"bytes": 26284
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-review-r1/README.md",
|
||||
"bytes": 6879
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-review-r1/fresh-inverse-session.candidate.patch",
|
||||
"bytes": 918
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-review-r1/original-authority-tests.txt",
|
||||
"bytes": 2708
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-review-r1/patched-authority-tests.txt",
|
||||
"bytes": 570
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-review-r1/review.json",
|
||||
"bytes": 6572
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-f3-review-r1/test_inverse_authority.py",
|
||||
"bytes": 2737
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/apply_prepare.py",
|
||||
"bytes": 1985
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/consistency.json",
|
||||
"bytes": 13861
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/consistency.stderr.txt",
|
||||
"bytes": 79
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/identifier-assignment.json",
|
||||
"bytes": 881
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/identifier-preview.json",
|
||||
"bytes": 882
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/preimage.json",
|
||||
"bytes": 783
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/projection.json",
|
||||
"bytes": 36744
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/projection.stderr.txt",
|
||||
"bytes": 0
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/publication-commit.json",
|
||||
"bytes": 872
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/publication-inputs.json",
|
||||
"bytes": 515
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/workplan.allocated.md",
|
||||
"bytes": 93587
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/workplan.candidate.md",
|
||||
"bytes": 93413
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-r5-source-followthrough-r1/workplan.preimage.md",
|
||||
"bytes": 88901
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/candidate.patch",
|
||||
"bytes": 15036
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/candidate/src/warden/case_run.py",
|
||||
"bytes": 41144
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/candidate/src/warden/case_type.py",
|
||||
"bytes": 6894
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/candidate/src/warden/routing/models.py",
|
||||
"bytes": 15339
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/candidate/tests/test_case_1_3_integration.py",
|
||||
"bytes": 6834
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/candidate/tests/test_case_admission_modes.py",
|
||||
"bytes": 5252
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/candidate/tests/test_case_attended_boundary.py",
|
||||
"bytes": 4831
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/evidence/run.py",
|
||||
"bytes": 641
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/evidence/sitecustomize.py",
|
||||
"bytes": 688
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/implementation-r1.json",
|
||||
"bytes": 7441
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-fixes-r1/test-isolation-proposal.patch",
|
||||
"bytes": 1803
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-review-r2/integration_runner.py",
|
||||
"bytes": 1212
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-review-r2/kernel-review-r2.json",
|
||||
"bytes": 11541
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-review-r2/reviewer-probes.py",
|
||||
"bytes": 4213
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-review-r2/run-baseline.py",
|
||||
"bytes": 542
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-review-r2/run.py",
|
||||
"bytes": 544
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-review-r2/setup.py",
|
||||
"bytes": 1766
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-1.3-kernel-review-r2/sitecustomize.py",
|
||||
"bytes": 879
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/commit.json",
|
||||
"bytes": 268
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/consistency-preimage.json",
|
||||
"bytes": 15107
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/consistency.json",
|
||||
"bytes": 5425
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/consistency.stderr.txt",
|
||||
"bytes": 0
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/focused-tests.txt",
|
||||
"bytes": 179
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/integrate.py",
|
||||
"bytes": 7327
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/integration.json",
|
||||
"bytes": 930
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/normal-sync.json",
|
||||
"bytes": 9997
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/normal-sync.stderr.txt",
|
||||
"bytes": 0
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/publish.py",
|
||||
"bytes": 4575
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/verification.json",
|
||||
"bytes": 1648
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-kernel-source-integration-r1/workplan.preimage.md",
|
||||
"bytes": 16634
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/README.md",
|
||||
"bytes": 3977
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate.patch",
|
||||
"bytes": 32117
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/Makefile",
|
||||
"bytes": 1957
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/docs/development/safe-unit-tests.md",
|
||||
"bytes": 4289
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/scripts/run_unit_tests.py",
|
||||
"bytes": 14899
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/tests/fixtures/workload-coverage/provenance.json",
|
||||
"bytes": 932
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/tests/fixtures/workload-coverage/rapp-core-hub/declarations/rapp.yaml",
|
||||
"bytes": 413
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/tests/fixtures/workload-coverage/rapp-issue-core/declarations/rapp.yaml",
|
||||
"bytes": 292
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/tests/fixtures/workload-coverage/rapp-qonto/declarations/rapp.yaml",
|
||||
"bytes": 284
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/tests/test_case_plan.py",
|
||||
"bytes": 8547
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/tests/test_case_type.py",
|
||||
"bytes": 5039
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/tests/test_layer_conformance.py",
|
||||
"bytes": 15990
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/tests/test_safe_unit_runner.py",
|
||||
"bytes": 3368
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/tests/test_workload_join.py",
|
||||
"bytes": 3081
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/tests/unit_isolation_probe.py",
|
||||
"bytes": 1079
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/candidate/workplans/WARDEN-WP-0044-security-case-follow-through.md",
|
||||
"bytes": 21045
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/implementation.json",
|
||||
"bytes": 6038
|
||||
},
|
||||
{
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/warden-safe-unit-runner-r1/packet-hashes.json",
|
||||
"bytes": 2647
|
||||
},
|
||||
{
|
||||
"path": "docs/netkingdom-target-implementation.md",
|
||||
"bytes": 80197
|
||||
},
|
||||
{
|
||||
"path": "docs/netkingdom-target-security-architecture.md",
|
||||
"bytes": 18310
|
||||
},
|
||||
{
|
||||
"path": "workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md",
|
||||
"bytes": 83344
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
[
|
||||
{
|
||||
"path": ".custodian-brief.md",
|
||||
"sha256": "74e3f825b14c6fe5a0f8cf511e2b85d9010f56109ea5bee700b360766b3dd453",
|
||||
"index": "100644 6aad3a5788fad3abd60c0f1270f160062dc0168b 0\t.custodian-brief.md"
|
||||
},
|
||||
{
|
||||
"path": "WORK-RECORDS.md",
|
||||
"sha256": "b8dc1ac2245e7aa59b13af1f62b0ca21b6c5704cafdb5a4de7a81b2d5595265b",
|
||||
"index": "100644 d535e0f2dcc3d454e041a8f1b547b5dea4493380 0\tWORK-RECORDS.md"
|
||||
},
|
||||
{
|
||||
"path": "docs/assessments/netkingdom-2026-10-14/hosted-before-federated.json",
|
||||
"sha256": "baecc157233131d17c73e84cd997db922d36ad235cbaa91f4938852220a824a2",
|
||||
"index": "100644 bbe2969551797f8c0c7c33e1009f1b293a631113 0\tdocs/assessments/netkingdom-2026-10-14/hosted-before-federated.json"
|
||||
},
|
||||
{
|
||||
"path": "docs/assessments/netkingdom-2026-10-14/hub-private-source-pending.json",
|
||||
"sha256": "92916fa2fcabbe3f4b7c4b89ddbd6dc6cb079ffd6f06cf66b2c6e6d6ff5fabde",
|
||||
"index": "100644 af6b3430909c8cbf8190c53cd541da651291151a 0\tdocs/assessments/netkingdom-2026-10-14/hub-private-source-pending.json"
|
||||
},
|
||||
{
|
||||
"path": "docs/assessments/netkingdom-2026-10-14/owner-access-gate.json",
|
||||
"sha256": "059d36b607383c1458dd2909e9adc5ec8be42af7c41412b4ce2d244d20d093df",
|
||||
"index": "100644 514edc14eb0a48736594bdd1bde2348e3d55e365 0\tdocs/assessments/netkingdom-2026-10-14/owner-access-gate.json"
|
||||
},
|
||||
{
|
||||
"path": "docs/assessments/netkingdom-2026-10-14/publication-preflight.json",
|
||||
"sha256": "4b041890abae585ca78536fa12bb9668392f21b7c2c21f5f581e2eed516a1164",
|
||||
"index": "100644 ee5730a4c683ee71240a4086e34a448cad92d88d 0\tdocs/assessments/netkingdom-2026-10-14/publication-preflight.json"
|
||||
},
|
||||
{
|
||||
"path": "docs/assessments/netkingdom-2026-10-14/published-source-verification.json",
|
||||
"sha256": "f0095fc7a8089443f27ad50e358247705a6a5ce83c85f4d57b402b116741e7c0",
|
||||
"index": "100644 83cdeb35d0ccd0b3a2472f8aa95767437566cbd9 0\tdocs/assessments/netkingdom-2026-10-14/published-source-verification.json"
|
||||
},
|
||||
{
|
||||
"path": "docs/assessments/netkingdom-2026-10-14/scoped-access-handoff.json",
|
||||
"sha256": "29c43c9909bb8a94ebdc74312a545b66bcc73de1a355158bf247cf8fc405f16b",
|
||||
"index": "100644 44097c5ea5ab08a0834ab1bd1d42fe4528bbb2c2 0\tdocs/assessments/netkingdom-2026-10-14/scoped-access-handoff.json"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
||||
# Custodian Brief — the-custodian
|
||||
|
||||
**Domain:** infotech
|
||||
**Last synced:** 2026-10-15 23:45 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Active Workstreams
|
||||
|
||||
### NetKingdom target security architecture and SEC-DSM implementation
|
||||
Progress: 2/6 done | workplan_id: `dfadb19b-445e-59c6-bcd6-48e87b912c52`
|
||||
|
||||
**Open tasks:**
|
||||
- ► Close source defects and reconcile uncovered lifecycle gaps `87637402`
|
||||
- ► Coordinate self-service Playwright identity delivery `ed8b909a`
|
||||
- ► Establish the target architecture and drive the restructuring `6aca534c`
|
||||
- · Verify native closure against every requirement `82378b0f`
|
||||
|
||||
### Separate agent credentials and establish supervised privileged execution
|
||||
Progress: 2/5 done | workplan_id: `a98a9f34-83b4-5c8c-8107-e05f7806d50d`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Build and hand out the agent identity `4b88b0b7`
|
||||
*(wait: Await GLAS-WP-0012 (glas-harness) production acceptance of the local supervised profile; then railiance-platform RBAC, railiance-enablement k3s config, railiance-infra principal mapping and ops-warden certificate issuance.)*
|
||||
- ! Fleet guidance and harness guards `90725511`
|
||||
*(wait: Await CUST-WP-0073-T02: the verified agent identity and execution path must exist before it can be documented; Codex/Grok read-denial guard needs glas-harness or harness-owner delivery.)*
|
||||
- ! Rotate what was exposed `1b41b532`
|
||||
*(wait: Deferred by founder decision 2026-09-24; reopened only by the founder on one of the three triggers below, in one attended rotation window.)*
|
||||
|
||||
### Size Railiance workloads from actual demand and establish a weekly allocation review
|
||||
Progress: 1/5 done | workplan_id: `2249bddb-7524-5add-bd5c-c4163a6ca0f3`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Measure the Vergabe prototype under representative pilot use `82192370`
|
||||
*(wait: Await RAPPS-WP-0014-T03 (blocked, needs_human): populated two-user/document workflow run by Bernd and the company contact, giving representative load for p95 <= 2s / zero failures.)*
|
||||
- ! Review ecosystem allocations and reserve operating room `6dc67558`
|
||||
*(wait: Await CUST-WP-0071-T02 representative sample; Forgejo/runner demand and zero-request workload review need owner input (railiance-cluster, railiance-forge, resource-control).)*
|
||||
- ! Apply accepted sizing changes and verify their effects `944f98e2`
|
||||
*(wait: Await reviewed allocation proposals and applicable workload-owner authority.)*
|
||||
- ! Establish the regular weekly workload-demand and allocation assessment `20ddd4d7`
|
||||
*(wait: Await the repeatable observation and accepted sizing/review procedure.)*
|
||||
|
||||
### Deterministic security flow: standing authorizations, flow report, and agent re-engagement
|
||||
Progress: 3/7 done | workplan_id: `ab288f92-d364-5c24-9362-aee451f266c3`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Schedule the flow report and decision sync `1be0569c`
|
||||
*(wait: Activity Core accepted the schedule in ACTIVITY-WP-0047. Await claiming rein workstation/hub acceptance, owner activation and the first unassisted fire with report/checkpoint evidence.)*
|
||||
- ! Encode the standing change rule as a policy-nexus guardrail `5dd52156`
|
||||
*(wait: policy-nexus owns guardrail publication; handoff written as PNEX-WP-0006.)*
|
||||
- ! Stop agent-authored index and sync commits `8a3dc219`
|
||||
*(wait: Local guard and repeatable shared-hook installation are implemented. Fleet generator adoption and the required full week of clean history remain unverified; repo-manager owner acceptance is needed.)*
|
||||
- ! Attended window #1 `e3da807f`
|
||||
*(wait: Partly done 2026-10-11: list cut from 14 to 3; U05-U07 accepted, U08 waits on KEY-WP-0040-T01. Remaining founder acts: USER-WP-0026-T03 readback, RPF-WP-0044-T08 coulombcore check, the U08 re-run, and Sol's added RPF-WP-0044-T04 issuer login.)*
|
||||
|
||||
---
|
||||
## 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.
|
||||
|
|
@ -0,0 +1,536 @@
|
|||
# Work Records — the-custodian
|
||||
|
||||
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
|
||||
> stage 3). Do not edit by hand — edit the source file/block listed for
|
||||
> each record and re-run fix-consistency to refresh this index. Archived
|
||||
> workplans are omitted; closed decisions/intakes/engagements stay listed
|
||||
> so recently-resolved work is still visible. [auto]
|
||||
|
||||
| Kind | ID | Status | Lane | Source |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| workplan | CUST-WP-ADHOC-2026-05-02 | done | — | workplans/ADHOC-2026-05-02.md |
|
||||
| workplan | CUST-WP-ADHOC-2026-07-02 | finished | — | workplans/ADHOC-2026-07-02.md |
|
||||
| workplan | CUST-WP-ADHOC-2026-08-25-RETIRED | finished | — | workplans/ADHOC-2026-08-25-retired.md |
|
||||
| workplan | CUST-WP-ADHOC-2026-08-25 | finished | — | workplans/ADHOC-2026-08-25.md |
|
||||
| workplan | CUST-WP-ADHOC-2026-09-06 | finished | — | workplans/ADHOC-2026-09-06.md |
|
||||
| workplan | CUST-WP-0000 | finished | — | workplans/CUST-WP-0000-state-hub-v0.1-build-deploy.md |
|
||||
| workplan | CUST-WP-0000b | finished | — | workplans/CUST-WP-0000b-state-hub-v0.2-decisions-dependencies.md |
|
||||
| workplan | CUST-WP-0001 | done | — | workplans/CUST-WP-0001-custodian-agent-runtime.md |
|
||||
| workplan | CUST-WP-0002 | finished | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| workplan | CUST-WP-0004 | finished | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| workplan | CUST-WP-0005 | finished | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| workplan | CUST-WP-0006 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
|
||||
| workplan | CUST-WP-0007 | finished | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| workplan | CUST-WP-0008 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
|
||||
| workplan | CUST-WP-0009 | finished | — | workplans/CUST-WP-0009-human-interventions.md |
|
||||
| workplan | CUST-WP-0010 | finished | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
|
||||
| workplan | CUST-WP-0010b | finished | — | workplans/CUST-WP-0010b-domain-and-repo-goals.md |
|
||||
| workplan | CUST-WP-0013 | finished | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| workplan | CUST-WP-0015 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
|
||||
| workplan | CUST-WP-0016 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
|
||||
| workplan | CUST-WP-0017 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
|
||||
| workplan | CUST-WP-0018 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
|
||||
| workplan | CUST-WP-0019 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
|
||||
| workplan | CUST-WP-0020 | done | — | workplans/CUST-WP-0020-test-suite.md |
|
||||
| workplan | CUST-WP-0021 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
|
||||
| workplan | CUST-WP-0022 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| workplan | CUST-WP-0023 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| workplan | CUST-WP-0024 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
|
||||
| workplan | CUST-WP-0026 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
|
||||
| workplan | CUST-WP-0027 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
|
||||
| workplan | CUST-WP-0029 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
|
||||
| workplan | CUST-WP-0030 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
|
||||
| workplan | CUST-WP-0031 | done | — | workplans/CUST-WP-0031-capability-registry.md |
|
||||
| workplan | CUST-WP-0032 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| workplan | CUST-WP-0033 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
|
||||
| workplan | CUST-WP-0039 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
|
||||
| workplan | CUST-WP-0040 | finished | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
|
||||
| workplan | CUST-WP-0041 | done | — | workplans/CUST-WP-0041-api-performance.md |
|
||||
| workplan | CUST-WP-0043 | finished | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
|
||||
| workplan | CUST-WP-0044 | finished | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
|
||||
| workplan | CUST-WP-0045 | finished | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
|
||||
| workplan | CUST-WP-0069 | finished | — | workplans/CUST-WP-0045-cutover-runbook.md |
|
||||
| workplan | CUST-WP-0046 | finished | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
|
||||
| workplan | CUST-WP-0047 | finished | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
|
||||
| workplan | CUST-WP-0048 | finished | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
|
||||
| workplan | CUST-WP-0049 | finished | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
|
||||
| workplan | CUST-WP-0050 | finished | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| workplan | CUST-WP-0051 | finished | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
|
||||
| workplan | CUST-WP-0052 | finished | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
|
||||
| workplan | CUST-WP-0053 | finished | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
|
||||
| workplan | CUST-WP-0055 | finished | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
|
||||
| workplan | CUST-WP-0056 | finished | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
|
||||
| workplan | CUST-WP-0059 | finished | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
|
||||
| workplan | CUST-WP-0060 | finished | — | workplans/CUST-WP-0060-work-record-canonization.md |
|
||||
| workplan | CUST-WP-0061 | finished | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
|
||||
| workplan | CUST-WP-0062 | finished | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
|
||||
| workplan | CUST-WP-0063 | finished | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
|
||||
| workplan | CUST-WP-0064 | finished | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
|
||||
| workplan | CUST-WP-0065 | finished | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
|
||||
| workplan | CUST-WP-0066 | finished | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
|
||||
| workplan | CUST-WP-0067 | finished | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| workplan | CUST-WP-0068 | finished | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
|
||||
| workplan | CUST-WP-0070 | finished | — | workplans/CUST-WP-0070-publication-repo-category.md |
|
||||
| workplan | CUST-WP-0071 | blocked | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
|
||||
| workplan | CUST-WP-0072 | finished | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
|
||||
| workplan | CUST-WP-0073 | blocked | — | workplans/CUST-WP-0073-agent-credential-separation.md |
|
||||
| workplan | CUST-WP-0074 | finished | — | workplans/CUST-WP-0074-qualified-wait-states.md |
|
||||
| workplan | CUST-WP-0075 | finished | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
|
||||
| workplan | CUST-WP-0076 | finished | — | workplans/CUST-WP-0076-provisioning-window-cadence.md |
|
||||
| workplan | CUST-WP-0077 | finished | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
|
||||
| workplan | CUST-WP-0078 | finished | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
|
||||
| workplan | CUST-WP-0079 | blocked | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| workplan | CUST-WP-0080 | active | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
|
||||
| workplan | CUST-WP-0081 | finished | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
|
||||
| workplan | THE-WP-0001 | finished | — | workplans/THE-WP-0001-federation-interface.md |
|
||||
| task | CUST-WP-ADHOC-2026-05-02-T01 | done | — | workplans/ADHOC-2026-05-02.md |
|
||||
| task | CUST-WP-ADHOC-2026-07-02-T01 | done | — | workplans/ADHOC-2026-07-02.md |
|
||||
| task | ADHOC-2026-08-25-T01 | done | — | workplans/ADHOC-2026-08-25-retired.md |
|
||||
| task | CUST-WP-ADHOC-2026-08-25-T01 | done | — | workplans/ADHOC-2026-08-25.md |
|
||||
| task | CUST-WP-ADHOC-2026-09-06-T01 | done | — | workplans/ADHOC-2026-09-06.md |
|
||||
| task | CUST-WP-0001-T01 | done | — | workplans/CUST-WP-0001-custodian-agent-runtime.md |
|
||||
| task | CUST-WP-0001-T02 | done | — | workplans/CUST-WP-0001-custodian-agent-runtime.md |
|
||||
| task | CUST-WP-0002-T01 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T02 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T03 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T04 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T05 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T06 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T07 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T08 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T09 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T10 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T11 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T12 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T13 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T14 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0002-T15 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
|
||||
| task | CUST-WP-0004-T01 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| task | CUST-WP-0004-T02 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| task | CUST-WP-0004-T03 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| task | CUST-WP-0004-T04 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| task | CUST-WP-0004-T05 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| task | CUST-WP-0004-T06 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| task | CUST-WP-0004-T07 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| task | CUST-WP-0004-T08 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| task | CUST-WP-0004-T09 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| task | CUST-WP-0004-T10 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
|
||||
| task | CUST-WP-0005-T01 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | CUST-WP-0005-T02 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | CUST-WP-0005-T03 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | CUST-WP-0005-T04 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | CUST-WP-0005-T05 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | CUST-WP-0005-T06 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | CUST-WP-0005-T07 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | CUST-WP-0005-T08 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | CUST-WP-0005-T09 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | CUST-WP-0005-T10 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | CUST-WP-0005-T11 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
|
||||
| task | T01 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
|
||||
| task | T02 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
|
||||
| task | T03 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
|
||||
| task | T04 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
|
||||
| task | T05 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
|
||||
| task | T01 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T02 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T03 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T04 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T05 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T06 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T07 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T08 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T09 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T10 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T11 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T12 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T13 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T14 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T15 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T16 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | T17 | done | — | workplans/CUST-WP-0007-gems-migration.md |
|
||||
| task | CUST-WP-0008-T01 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
|
||||
| task | CUST-WP-0008-T02 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
|
||||
| task | CUST-WP-0008-T03 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
|
||||
| task | CUST-WP-0008-T04 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
|
||||
| task | CUST-WP-0009-T1 | done | — | workplans/CUST-WP-0009-human-interventions.md |
|
||||
| task | CUST-WP-0009-T2 | done | — | workplans/CUST-WP-0009-human-interventions.md |
|
||||
| task | CUST-WP-0009-T3 | done | — | workplans/CUST-WP-0009-human-interventions.md |
|
||||
| task | CUST-WP-0010-T1 | done | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
|
||||
| task | CUST-WP-0010-T2 | done | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
|
||||
| task | CUST-WP-0010-T3 | done | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
|
||||
| task | CUST-WP-0010b-T01 | done | — | workplans/CUST-WP-0010b-domain-and-repo-goals.md |
|
||||
| task | CUST-WP-0010b-T02 | done | — | workplans/CUST-WP-0010b-domain-and-repo-goals.md |
|
||||
| task | CUST-WP-0013-T01 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T02 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T03 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T04 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T05 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T06 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T07 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T08 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T09 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T10 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T11 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T12 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T13 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0013-T14 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
|
||||
| task | CUST-WP-0015-T01 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
|
||||
| task | CUST-WP-0015-T02 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
|
||||
| task | CUST-WP-0015-T03 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
|
||||
| task | CUST-WP-0015-T04 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
|
||||
| task | CUST-WP-0015-T05 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
|
||||
| task | CUST-WP-0016-T01 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
|
||||
| task | CUST-WP-0016-T02 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
|
||||
| task | CUST-WP-0016-T03 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
|
||||
| task | CUST-WP-0016-T04 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
|
||||
| task | CUST-WP-0016-T05 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
|
||||
| task | CUST-WP-0017-T01 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
|
||||
| task | CUST-WP-0017-T02 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
|
||||
| task | CUST-WP-0017-T03 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
|
||||
| task | CUST-WP-0017-T04 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
|
||||
| task | CUST-WP-0017-T05 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
|
||||
| task | CUST-WP-0017-T06 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
|
||||
| task | CUST-WP-0018-T01 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
|
||||
| task | CUST-WP-0018-T02 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
|
||||
| task | CUST-WP-0018-T03 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
|
||||
| task | CUST-WP-0018-T04 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
|
||||
| task | CUST-WP-0018-T05 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
|
||||
| task | CUST-WP-0018-T06 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
|
||||
| task | CUST-WP-0018-T07 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
|
||||
| task | CUST-WP-0019-T01 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
|
||||
| task | CUST-WP-0019-T02 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
|
||||
| task | CUST-WP-0019-T03 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
|
||||
| task | CUST-WP-0020-T01 | done | — | workplans/CUST-WP-0020-test-suite.md |
|
||||
| task | CUST-WP-0020-T02 | done | — | workplans/CUST-WP-0020-test-suite.md |
|
||||
| task | CUST-WP-0020-T03 | done | — | workplans/CUST-WP-0020-test-suite.md |
|
||||
| task | CUST-WP-0020-T04 | done | — | workplans/CUST-WP-0020-test-suite.md |
|
||||
| task | CUST-WP-0020-T05 | done | — | workplans/CUST-WP-0020-test-suite.md |
|
||||
| task | CUST-WP-0021-T01 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
|
||||
| task | CUST-WP-0021-T02 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
|
||||
| task | CUST-WP-0021-T03 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
|
||||
| task | CUST-WP-0021-T04 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
|
||||
| task | CUST-WP-0021-T05 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
|
||||
| task | CUST-WP-0022-T01 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| task | CUST-WP-0022-T02 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| task | CUST-WP-0022-T03 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| task | CUST-WP-0022-T04 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| task | CUST-WP-0022-T05 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| task | CUST-WP-0022-T06 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| task | CUST-WP-0022-T07 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| task | CUST-WP-0022-T08 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| task | CUST-WP-0022-T09 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| task | CUST-WP-0022-T10 | done | — | workplans/CUST-WP-0022-capability-requests.md |
|
||||
| task | CUST-WP-0023-T01 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| task | CUST-WP-0023-T02 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| task | CUST-WP-0023-T03 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| task | CUST-WP-0023-T04 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| task | CUST-WP-0023-T05 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| task | CUST-WP-0023-T06 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| task | CUST-WP-0023-T07 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| task | CUST-WP-0023-T08 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| task | CUST-WP-0023-T09 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| task | CUST-WP-0023-T10 | done | — | workplans/CUST-WP-0023-tpsc.md |
|
||||
| task | CUST-WP-0024-T01 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
|
||||
| task | CUST-WP-0024-T02 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
|
||||
| task | CUST-WP-0024-T03 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
|
||||
| task | CUST-WP-0024-T04 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
|
||||
| task | CUST-WP-0024-T05 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
|
||||
| task | CUST-WP-0026-T01 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
|
||||
| task | CUST-WP-0026-T02 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
|
||||
| task | CUST-WP-0026-T03 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
|
||||
| task | CUST-WP-0026-T04 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
|
||||
| task | CUST-WP-0026-T05 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
|
||||
| task | CUST-WP-0027-T01 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
|
||||
| task | CUST-WP-0027-T02 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
|
||||
| task | CUST-WP-0027-T03 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
|
||||
| task | CUST-WP-0027-T04 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
|
||||
| task | CUST-WP-0027-T05 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
|
||||
| task | CUST-WP-0029-T01 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
|
||||
| task | CUST-WP-0029-T02 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
|
||||
| task | CUST-WP-0029-T03 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
|
||||
| task | CUST-WP-0029-T04 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
|
||||
| task | CUST-WP-0029-T05 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
|
||||
| task | CUST-WP-0029-T06 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
|
||||
| task | CUST-WP-0029-T07 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
|
||||
| task | CUST-WP-0029-T08 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
|
||||
| task | T01 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
|
||||
| task | T02 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
|
||||
| task | T03 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
|
||||
| task | T04 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
|
||||
| task | T05 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
|
||||
| task | T06 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
|
||||
| task | T07 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
|
||||
| task | T08 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
|
||||
| task | T09 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
|
||||
| task | T01 | done | — | workplans/CUST-WP-0031-capability-registry.md |
|
||||
| task | T02 | done | — | workplans/CUST-WP-0031-capability-registry.md |
|
||||
| task | T03 | done | — | workplans/CUST-WP-0031-capability-registry.md |
|
||||
| task | T04 | done | — | workplans/CUST-WP-0031-capability-registry.md |
|
||||
| task | T05 | done | — | workplans/CUST-WP-0031-capability-registry.md |
|
||||
| task | T06 | done | — | workplans/CUST-WP-0031-capability-registry.md |
|
||||
| task | T07 | done | — | workplans/CUST-WP-0031-capability-registry.md |
|
||||
| task | CUST-WP-0032-T01 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T02 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T03 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T04 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T05 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T06 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T07 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T08 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T09 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T10 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T11 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T12 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T13 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T14 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0032-T15 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
|
||||
| task | CUST-WP-0033-T01 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
|
||||
| task | CUST-WP-0033-T02 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
|
||||
| task | CUST-WP-0033-T03 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
|
||||
| task | CUST-WP-0033-T04 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
|
||||
| task | CUST-WP-0033-T05 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
|
||||
| task | CUST-WP-0033-T06 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
|
||||
| task | CUST-WP-0039-T1 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
|
||||
| task | CUST-WP-0039-T2 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
|
||||
| task | CUST-WP-0039-T3 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
|
||||
| task | CUST-WP-0039-T4 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
|
||||
| task | CUST-WP-0039-T5 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
|
||||
| task | CUST-WP-0039-T6 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
|
||||
| task | T01 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
|
||||
| task | T02 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
|
||||
| task | T03 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
|
||||
| task | T04 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
|
||||
| task | T05 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
|
||||
| task | T06 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
|
||||
| task | CUST-WP-0041-T1 | done | — | workplans/CUST-WP-0041-api-performance.md |
|
||||
| task | CUST-WP-0041-T2 | done | — | workplans/CUST-WP-0041-api-performance.md |
|
||||
| task | CUST-WP-0041-T3 | done | — | workplans/CUST-WP-0041-api-performance.md |
|
||||
| task | CUST-WP-0041-T4 | done | — | workplans/CUST-WP-0041-api-performance.md |
|
||||
| task | CUST-WP-0043-T01 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
|
||||
| task | CUST-WP-0043-T02 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
|
||||
| task | CUST-WP-0043-T03 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
|
||||
| task | CUST-WP-0043-T04 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
|
||||
| task | CUST-WP-0043-T05 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
|
||||
| task | CUST-WP-0043-T06 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
|
||||
| task | CUST-WP-0043-T07 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
|
||||
| task | CUST-WP-0043-T08 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
|
||||
| task | CUST-WP-0043-T09 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
|
||||
| task | CUST-WP-0044-T01 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
|
||||
| task | CUST-WP-0044-T02 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
|
||||
| task | CUST-WP-0044-T03 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
|
||||
| task | CUST-WP-0044-T04 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
|
||||
| task | CUST-WP-0044-T05 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
|
||||
| task | CUST-WP-0044-T06 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
|
||||
| task | CUST-WP-0045-T01 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
|
||||
| task | CUST-WP-0045-T02 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
|
||||
| task | CUST-WP-0045-T03 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
|
||||
| task | CUST-WP-0045-T04 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
|
||||
| task | CUST-WP-0045-T05 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
|
||||
| task | CUST-WP-0045-T06 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
|
||||
| task | CUST-WP-0045-T07 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
|
||||
| task | CUST-WP-0045-T08 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
|
||||
| task | CUST-WP-0046-T01 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
|
||||
| task | CUST-WP-0046-T02 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
|
||||
| task | CUST-WP-0046-T03 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
|
||||
| task | CUST-WP-0046-T04 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
|
||||
| task | CUST-WP-0046-T05 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
|
||||
| task | CUST-WP-0046-T06 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
|
||||
| task | CUST-WP-0046-T07 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
|
||||
| task | CUST-WP-0047-T01 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
|
||||
| task | CUST-WP-0047-T02 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
|
||||
| task | CUST-WP-0047-T03 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
|
||||
| task | CUST-WP-0047-T04 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
|
||||
| task | CUST-WP-0047-T05 | cancel | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
|
||||
| task | CUST-WP-0047-T06 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
|
||||
| task | CUST-WP-0047-T07 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
|
||||
| task | CUST-WP-0048-T01 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
|
||||
| task | CUST-WP-0048-T02 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
|
||||
| task | CUST-WP-0048-T03 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
|
||||
| task | CUST-WP-0048-T04 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
|
||||
| task | CUST-WP-0048-T05 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
|
||||
| task | CUST-WP-0048-T06 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
|
||||
| task | CUST-WP-0048-T07 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
|
||||
| task | CUST-WP-0048-T08 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
|
||||
| task | CUST-WP-0049-T01 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
|
||||
| task | CUST-WP-0049-T02 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
|
||||
| task | CUST-WP-0049-T03 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
|
||||
| task | CUST-WP-0049-T04 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
|
||||
| task | CUST-WP-0049-T05 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
|
||||
| task | CUST-WP-0049-T06 | cancel | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
|
||||
| task | CUST-WP-0050-T01 | done | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0050-T02 | done | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0050-T03 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0050-T04 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0050-T05 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0050-T06 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0050-T07 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0050-T08 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0050-T09 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0050-T10 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0050-T11 | done | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
|
||||
| task | CUST-WP-0051-T01 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
|
||||
| task | CUST-WP-0051-T02 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
|
||||
| task | CUST-WP-0051-T03 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
|
||||
| task | CUST-WP-0051-T04 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
|
||||
| task | CUST-WP-0051-T05 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
|
||||
| task | CUST-WP-0051-T06 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
|
||||
| task | CUST-WP-0051-T07 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
|
||||
| task | CUST-WP-0051-T08 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
|
||||
| task | CUST-WP-0052-T01 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
|
||||
| task | CUST-WP-0052-T02 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
|
||||
| task | CUST-WP-0052-T03 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
|
||||
| task | CUST-WP-0052-T04 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
|
||||
| task | CUST-WP-0052-T05 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
|
||||
| task | CUST-WP-0052-T06 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
|
||||
| task | CUST-WP-0052-T07 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
|
||||
| task | CUST-WP-0053-T01 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
|
||||
| task | CUST-WP-0053-T02 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
|
||||
| task | CUST-WP-0053-T03 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
|
||||
| task | CUST-WP-0053-T04 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
|
||||
| task | CUST-WP-0053-T05 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
|
||||
| task | CUST-WP-0055-T01 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
|
||||
| task | CUST-WP-0055-T02 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
|
||||
| task | CUST-WP-0055-T03 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
|
||||
| task | CUST-WP-0055-T04 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
|
||||
| task | CUST-WP-0055-T05 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
|
||||
| task | CUST-WP-0055-T06 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
|
||||
| task | CUST-WP-0055-T07 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
|
||||
| task | CUST-WP-0055-T08 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
|
||||
| task | CUST-WP-0056-T01 | done | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
|
||||
| task | CUST-WP-0056-T02 | done | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
|
||||
| task | CUST-WP-0056-T03 | done | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
|
||||
| task | CUST-WP-0059-T01 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
|
||||
| task | CUST-WP-0059-T02 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
|
||||
| task | CUST-WP-0059-T03 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
|
||||
| task | CUST-WP-0059-T04 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
|
||||
| task | CUST-WP-0059-T05 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
|
||||
| task | CUST-WP-0059-T06 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
|
||||
| task | CUST-WP-0060-T01 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
|
||||
| task | CUST-WP-0060-T02 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
|
||||
| task | CUST-WP-0060-T03 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
|
||||
| task | CUST-WP-0060-T04 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
|
||||
| task | CUST-WP-0060-T05 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
|
||||
| task | CUST-WP-0060-T06 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
|
||||
| task | CUST-WP-0060-T07 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
|
||||
| task | CUST-WP-0061-T01 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
|
||||
| task | CUST-WP-0061-T02 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
|
||||
| task | CUST-WP-0061-T03 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
|
||||
| task | CUST-WP-0061-T04 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
|
||||
| task | CUST-WP-0061-T05 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
|
||||
| task | CUST-WP-0061-T06 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
|
||||
| task | CUST-WP-0062-T01 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
|
||||
| task | CUST-WP-0062-T02 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
|
||||
| task | CUST-WP-0062-T03 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
|
||||
| task | CUST-WP-0062-T04 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
|
||||
| task | CUST-WP-0062-T05 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
|
||||
| task | CUST-WP-0062-T06 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
|
||||
| task | CUST-WP-0063-T01 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
|
||||
| task | CUST-WP-0063-T02 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
|
||||
| task | CUST-WP-0063-T03 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
|
||||
| task | CUST-WP-0063-T04 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
|
||||
| task | CUST-WP-0063-T05 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
|
||||
| task | CUST-WP-0064-T01 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
|
||||
| task | CUST-WP-0064-T02 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
|
||||
| task | CUST-WP-0064-T03 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
|
||||
| task | CUST-WP-0064-T04 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
|
||||
| task | CUST-WP-0065-T01 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
|
||||
| task | CUST-WP-0065-T02 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
|
||||
| task | CUST-WP-0065-T03 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
|
||||
| task | CUST-WP-0065-T04 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
|
||||
| task | CUST-WP-0066-T01 | done | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
|
||||
| task | CUST-WP-0066-T02 | done | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
|
||||
| task | CUST-WP-0066-T03 | done | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
|
||||
| task | CUST-WP-0067-T01 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| task | CUST-WP-0067-T02 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| task | CUST-WP-0067-T03 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| task | CUST-WP-0067-T04 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| task | CUST-WP-0067-T05 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| task | CUST-WP-0067-T06 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| task | CUST-WP-0067-T07 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| task | CUST-WP-0067-T08 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| task | CUST-WP-0067-T09 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| task | CUST-WP-0067-T10 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
|
||||
| task | CUST-WP-0068-T01 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
|
||||
| task | CUST-WP-0068-T02 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
|
||||
| task | CUST-WP-0068-T03 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
|
||||
| task | CUST-WP-0068-T04 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
|
||||
| task | CUST-WP-0068-T05 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
|
||||
| task | CUST-WP-0068-T06 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
|
||||
| task | CUST-WP-0068-T07 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
|
||||
| task | CUST-WP-0068-T08 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
|
||||
| task | CUST-WP-0068-T09 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
|
||||
| task | CUST-WP-0070-T01 | done | — | workplans/CUST-WP-0070-publication-repo-category.md |
|
||||
| task | CUST-WP-0070-T02 | done | — | workplans/CUST-WP-0070-publication-repo-category.md |
|
||||
| task | CUST-WP-0070-T03 | done | — | workplans/CUST-WP-0070-publication-repo-category.md |
|
||||
| task | CUST-WP-0071-T01 | done | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
|
||||
| task | CUST-WP-0071-T02 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
|
||||
| task | CUST-WP-0071-T03 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
|
||||
| task | CUST-WP-0071-T04 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
|
||||
| task | CUST-WP-0071-T05 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
|
||||
| task | CUST-WP-0072-T01 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
|
||||
| task | CUST-WP-0072-T02 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
|
||||
| task | CUST-WP-0072-T03 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
|
||||
| task | CUST-WP-0072-T04 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
|
||||
| task | CUST-WP-0073-T01 | done | — | workplans/CUST-WP-0073-agent-credential-separation.md |
|
||||
| task | CUST-WP-0073-T02 | wait | — | workplans/CUST-WP-0073-agent-credential-separation.md |
|
||||
| task | CUST-WP-0073-T03 | done | — | workplans/CUST-WP-0073-agent-credential-separation.md |
|
||||
| task | CUST-WP-0073-T04 | wait | — | workplans/CUST-WP-0073-agent-credential-separation.md |
|
||||
| task | CUST-WP-0073-T05 | wait | — | workplans/CUST-WP-0073-agent-credential-separation.md |
|
||||
| task | CUST-WP-0074-T01 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
|
||||
| task | CUST-WP-0074-T02 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
|
||||
| task | CUST-WP-0074-T03 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
|
||||
| task | CUST-WP-0074-T04 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
|
||||
| task | CUST-WP-0074-T05 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
|
||||
| task | CUST-WP-0074-T06 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
|
||||
| task | CUST-WP-0075-T01 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
|
||||
| task | CUST-WP-0075-T02 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
|
||||
| task | CUST-WP-0075-T03 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
|
||||
| task | CUST-WP-0075-T04 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
|
||||
| task | CUST-WP-0075-T05 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
|
||||
| task | CUST-WP-0075-T06 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
|
||||
| task | CUST-WP-0076-T01 | done | — | workplans/CUST-WP-0076-provisioning-window-cadence.md |
|
||||
| task | CUST-WP-0077-T01 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
|
||||
| task | CUST-WP-0077-T02 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
|
||||
| task | CUST-WP-0077-T03 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
|
||||
| task | CUST-WP-0077-T04 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
|
||||
| task | CUST-WP-0077-T05 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
|
||||
| task | CUST-WP-0078-T01 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
|
||||
| task | CUST-WP-0078-T02 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
|
||||
| task | CUST-WP-0078-T03 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
|
||||
| task | CUST-WP-0078-T04 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
|
||||
| task | CUST-WP-0079-T01 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T02 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T03 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T04 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T05 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T06 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T07 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0080-T01 | done | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
|
||||
| task | CUST-WP-0080-T02 | done | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
|
||||
| task | CUST-WP-0080-T03 | progress | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
|
||||
| task | CUST-WP-0080-T04 | todo | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
|
||||
| task | CUST-WP-0080-T05 | progress | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
|
||||
| task | CUST-WP-0080-T06 | progress | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
|
||||
| task | CUST-WP-0081-T01 | done | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
|
||||
| task | CUST-WP-0081-T02 | done | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
|
||||
| task | CUST-WP-0081-T03 | done | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
|
||||
| task | THE-WP-0001-T01 | done | — | workplans/THE-WP-0001-federation-interface.md |
|
||||
| task | THE-WP-0001-T02 | done | — | workplans/THE-WP-0001-federation-interface.md |
|
||||
| intake | CUST-IN-0001 | closed | green | intake-legacy-suggestions-migration.md |
|
||||
| intake | CUST-IN-0002 | closed | green | intake-legacy-suggestions-migration.md |
|
||||
| intake | CUST-IN-0003 | closed | green | intake-legacy-suggestions-migration.md |
|
||||
| intake | CUST-IN-0004 | routed | green | intake-legacy-suggestions-migration.md |
|
||||
| intake | CUST-IN-0005 | closed | green | intake-legacy-suggestions-migration.md |
|
||||
| intake | CUST-IN-0006 | closed | green | intake-legacy-suggestions-migration.md |
|
||||
| intake | CUST-IN-0007 | closed | green | intake-legacy-suggestions-migration.md |
|
||||
| intake | CUST-IN-0008 | closed | blue | intake-legacy-suggestions-migration.md |
|
||||
| intake | CUST-IN-0009 | closed | blue | intake-legacy-suggestions-migration.md |
|
||||
| intake | CUST-IN-0010 | closed | green | intake-legacy-suggestions-migration.md |
|
||||
| intake | CUST-IN-0011 | routed | red | intakes.md |
|
||||
| intake | CUST-IN-0012 | closed | green | intakes.md |
|
||||
| intake | CUST-IN-0013 | closed | blue | intakes.md |
|
||||
| intake | CUST-IN-0014 | closed | blue | intakes.md |
|
||||
| intake | CUST-IN-0015 | open | blue | intakes.md |
|
||||
| intake | CUST-IN-0016 | open | green | intakes.md |
|
||||
| intake | CUST-IN-0017 | open | green | intakes.md |
|
||||
| intake | CUST-IN-0018 | open | green | intakes.md |
|
||||
Loading…
Add table
Add a link
Reference in a new issue