Prepare INFD-WP-0002-T03 sitting without a fake bind.

T08 historically bound three SECRETS-WP-0010-T03 memos on this origin.
Live accept is closed: /readyz 503 because audit-core has no ready
endpoints. Compact drafts still lack approval_id. Operator packet and
preflight record the gates; T03 stays wait.

Assistant: grok
Assistant-Session: 01a09dc1-b21e-77e1-919e-fcad2f82b267
This commit is contained in:
tegwick 2026-09-14 18:59:49 +02:00
parent 98aa4547a9
commit 6a386dd787
8 changed files with 413 additions and 3 deletions

View file

@ -0,0 +1,74 @@
# Compact sitting — operator packet (INFD-WP-0002-T03)
Review group: `net-kingdom-admins`.
Surface: https://decisions.coulomb.social/
Contract: `docs/specs/CompactSignoffBatches.md`.
An agent may draft, order, and record evidence. It may not `accept` / `decline` / `seal`.
Do not mark INFD-WP-0002 finished on a partial sitting.
## What is already proven
On 2026-09-14 around 01:06 UTC this origin took **three real human accepts**
for `SECRETS-WP-0010-T03-{apply,verify,exec}`: presentations, required-highlight
acknowledgments, `accept` dispositions, and confirmed approval-engine
submissions are in the review store. That discharges “can a human bind through
this surface?” for T08 historically.
Those three memos are **not** this sitting. `infd-20260914-c01` asks a different
question (admit native delivery) and must not reuse those approval ids.
## What is closed right now (2026-09-14 16:56 UTC)
Live accept is refused:
- Origin `/healthz` 200; `/auth/start` 303 to KeyCape (`informed-decision-approver`, PKCE S256).
- Origin `/readyz` **503** `approval_path_not_connected`.
- `audit-core` pod Ready=false; `/readyz` hangs; Service has no ready endpoints;
the review pod gets connection refused talking to `audit-core.audit-core.svc:8080`.
- Accept requires recent audit delivery health. Until that Service has a ready
endpoint, a new sitting cannot submit entries.
Evidence: `docs/evidence/2026-09-14-infd-0002-t03-bind-path-probe.json`.
Audit-core owns the hang; this packet does not change that deployment.
## Eight unsigned memos (this sitting)
Open in this order. One question each. No approve-all.
| n | memo_id | Blocking record |
| --: | --- | --- |
| 1 | `infd-20260914-c01` | SECRETS-WP-0010 native delivery |
| 2 | `infd-20260914-c02` | RPF-WP-0035-T02 |
| 3 | `infd-20260914-c03` | NK-WP-0032-T03 |
| 4 | `infd-20260914-c04` | WARDEN-WP-0027-T02 |
| 5 | `infd-20260914-d01` | CUST-WP-0038-T08 |
| 6 | `infd-20260914-d02` | HFACT-WP-0001-T03 |
| 7 | `infd-20260914-d03` | MASON-WP-0005 plan |
| 8 | `infd-20260914-d04` | RCLK-WP-0002-T01 |
Drafts still have `approval_id: null` and principal `pending-human-session`.
The live controller refuses those with `missing_act_binding` / `wrong_recipient`.
They are not in the live store.
## After audit-core is Ready
1. Confirm `GET https://decisions.coulomb.social/readyz` returns 200, or run
`uv run python tools/sitting_bind_preflight.py --origin https://decisions.coulomb.social`.
2. For **each** memo, the owning requester creates one `approval-engine` object
with `human_control=true`, required_count 1, no entries. Do **not** reuse
`secrets-engine-requester` for unrelated acts (WSL2 retirement, clock
ownership, mason plan, …).
3. Substitute the operators live KeyCape `sub` for `pending-human-session`.
Carry the native `approval_id` and `binding.digest` on the memo. Do not
invent a digest.
4. Load packet bytes then `save_memo` into the review store (named-memo form,
no inbox).
5. Sign in at the origin. Open `/review?memo_id=infd-20260914-c01` through
`…-d04` in order. Acknowledge the required highlight. Bind that memo. Repeat.
6. Unfinished memos stay in the batch. File-level owning-repo updates are T04
after a human bind, via `fix-consistency`, never `POST /workplans/`.
A recorded desktop sitting (same order, same highlights, `view_hash` from
`presentation.render` only) is allowed if the Stage 1 UI still cannot take the
group. It is still a human bind. An agent must not complete the form.

View file

@ -18,4 +18,10 @@ Two batches, eight memos, one compact sitting:
Contract: `docs/specs/CompactSignoffBatches.md`. Contract: `docs/specs/CompactSignoffBatches.md`.
Regenerate: `uv run python tools/prepare_compact_batches.py`. Regenerate: `uv run python tools/prepare_compact_batches.py`.
Live bind waits on `INFD-WP-0001-T08`. Preflight: `uv run python tools/sitting_bind_preflight.py --origin https://decisions.coulomb.social`.
Operator packet: `OPERATOR.md`.
T08 already took three human accepts on this origin for `SECRETS-WP-0010-T03-*`.
Those memos are not this sitting. Live accept is currently closed
(`/readyz` 503) because audit-core has no ready Service endpoints.
This sitting stays `draft-unsigned` until a human binds through the surface.

View file

@ -8,6 +8,6 @@
"infd-batch-2026-09-14-decisions" "infd-batch-2026-09-14-decisions"
], ],
"memo_count": 8, "memo_count": 8,
"bind_path": "INFD-WP-0001-T08 (not yet complete)", "bind_path": "INFD-WP-0001-T08 historically proven; live accept closed 2026-09-14T16:56:55Z",
"note": "Unsigned drafts. Do not submit until CompactSignoffBatches.md is accepted. Live bind substitutes pending-human-session with the authenticated key-cape subject." "note": "Unsigned drafts. Do not submit until a human sitting is opened under T03. Live bind substitutes pending-human-session with the authenticated key-cape subject. Probe: docs/evidence/2026-09-14-infd-0002-t03-bind-path-probe.json. Operator packet: OPERATOR.md."
} }

View file

@ -0,0 +1,83 @@
{
"kind": "infd-wp-0002-t03-bind-path-probe",
"observed_at": "2026-09-14T16:56:55Z",
"workplan": "INFD-WP-0002",
"task": "INFD-WP-0002-T03",
"scope": "Live bind-path diagnosis for the compact sitting. No human login, no presentation, no disposition, no approval create, no secret read.",
"origin": {
"url": "https://decisions.coulomb.social/",
"tls_verify": 0,
"healthz": {"http": 200, "body": {"status": "ok"}},
"readyz": {
"http": 503,
"body": {"status": "incomplete", "reason": "approval_path_not_connected"}
},
"auth_start": {
"http": 303,
"location_host": "kc.coulomb.social",
"client_id": "informed-decision-approver",
"redirect_uri": "https://decisions.coulomb.social/auth/callback",
"scopes": ["openid", "approval:read", "approval:approve"],
"pkce": "S256"
},
"unsigned_home": "Sign in with KeyCape only; memo form is session-gated"
},
"cluster": {
"informed-decision": {
"namespace": "informed-decision",
"pod": "informed-decision-b7f6fc6c9-kd4bl",
"ready": true,
"start_time": "2026-09-14T01:30:01Z",
"image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:8f55bcecf37a8d65f96e073510b1ffb4636c0a91d75e1ee7d582ad4bce8b953a"
},
"approval-engine": {
"namespace": "approval-engine",
"pod": "approval-engine-0",
"ready": true,
"in_cluster_healthz": {"http": 200, "body": {"status": "ok"}}
},
"audit-core": {
"namespace": "audit-core",
"pod": "audit-core-7cb66768cf-pq6pw",
"ready": false,
"started_at": "2026-09-11T08:27:26Z",
"restarts": 0,
"local_healthz": {"http": 200},
"local_readyz": "timeout (>8s); kubelet probe timeoutSeconds=2 on /readyz",
"service_cluster_ip": "10.43.74.236",
"classic_endpoints": "no ready addresses; one notReadyAddresses entry",
"from_informed_decision_pod": "connection refused to audit-core.audit-core.svc:8080"
}
},
"review_store_counts": {
"schema_version": 2,
"sqlite_mtime_in_pod": "2026-09-14T01:06:00Z approximately",
"memos": 3,
"presentations": 3,
"acknowledgments": 3,
"dispositions": {"accept": 3},
"submissions": {"confirmed": 3},
"outbox": {"delivered": 15},
"memo_ids": [
"SECRETS-WP-0010-T03-apply",
"SECRETS-WP-0010-T03-exec",
"SECRETS-WP-0010-T03-verify"
],
"note": "Counts and identifiers only. No packet bytes, principals, view_hash values, or approval entry bodies were copied out of the store."
},
"compact_sitting": {
"id": "infd-sitting-2026-09-14",
"status": "draft-unsigned",
"memo_count": 8,
"approval_ids": "all null",
"principal": "pending-human-session",
"in_live_store": false
},
"conclusion": {
"t08_historical_bind": "Three real human accept dispositions with confirmed engine submissions already exist on this surface for the SECRETS-WP-0010-T03 named memos. That sitting is not the INFD-WP-0002 compact batch.",
"t03_live_accept": "closed",
"reason": "informed-decision /readyz is 503 because audit-core is not Ready, its Service has no ready endpoints, and accept is refused while pump.ready() is false.",
"agent_disposition": "forbidden",
"infd_wp_0002_t03": "wait"
}
}

View file

@ -50,3 +50,11 @@ a human bind (T04), via repo files + `fix-consistency`, never
`docs/batches/2026-09-14/` — four credential/custody memos and four `docs/batches/2026-09-14/` — four credential/custody memos and four
decision/assent memos. Indexes name the blocking workplan/task. Do not decision/assent memos. Indexes name the blocking workplan/task. Do not
submit until a human sitting is opened under T03. submit until a human sitting is opened under T03.
2026-09-14 16:56 UTC: Stage 1 historically bound three other memos
(`SECRETS-WP-0010-T03-*`) on this origin. New accepts are closed while
`GET /readyz` returns 503 (`approval_path_not_connected`) because
audit-core is not Ready and its Service has no ready endpoints. Compact
memos still have `approval_id: null`. Operator packet:
`docs/batches/2026-09-14/OPERATOR.md`. Preflight:
`tools/sitting_bind_preflight.py`.

View file

@ -0,0 +1,78 @@
import importlib.util
from io import BytesIO
from pathlib import Path
spec = importlib.util.spec_from_file_location(
"sitting_bind_preflight",
Path(__file__).resolve().parents[1] / "tools" / "sitting_bind_preflight.py",
)
preflight = importlib.util.module_from_spec(spec)
spec.loader.exec_module(preflight)
evaluate, load_sitting, probe_origin = preflight.evaluate, preflight.load_sitting, preflight.probe_origin
class _Resp:
def __init__(self, status, body=b"", headers=None):
self.status = status
self.headers = headers or {}
self._body = BytesIO(body)
def getcode(self):
return self.status
def read(self, n=-1):
return self._body.read(n)
def __enter__(self):
return self
def __exit__(self, *exc):
return False
def test_unsigned_sitting_is_not_ready_to_sit():
report = evaluate(load_sitting())
assert report["ready_to_sit"] is False
assert report["agent_disposition"] == "forbidden"
assert report["memo_count"] == 8
assert "missing_act_binding" in report["gates"]
assert "principal_not_live_subject" in report["gates"]
assert len(report["missing_act_binding"]) == 8
assert report["live_accept"] is None
def test_origin_readyz_503_is_live_accept_closed():
bodies = {
"/healthz": _Resp(200, b'{"status":"ok"}'),
"/readyz": _Resp(503, b'{"status":"incomplete","reason":"approval_path_not_connected"}'),
"/auth/start": _Resp(303, b"", {"Location": "https://kc.coulomb.social/authorize?client_id=informed-decision-approver"}),
}
def opener(req, timeout=10):
return bodies[req.full_url.split("https://decisions.coulomb.social", 1)[1]]
probe = probe_origin("https://decisions.coulomb.social", opener=opener)
report = evaluate(load_sitting(), probe)
assert report["live_accept"] == "closed"
assert "live_accept_closed" in report["gates"]
assert report["ready_to_sit"] is False
assert probe["auth_start"]["http"] == 303
assert probe["auth_start"]["location_host"] == "kc.coulomb.social"
assert "body" not in probe["auth_start"]
def test_origin_ready_still_blocked_on_unsigned_memos():
bodies = {
"/healthz": _Resp(200, b'{"status":"ok"}'),
"/readyz": _Resp(200, b'{"status":"ready"}'),
"/auth/start": _Resp(303, b"", {"Location": "https://kc.example/authorize"}),
}
def opener(req, timeout=10):
return bodies[req.full_url.split("https://origin.example", 1)[1]]
report = evaluate(load_sitting(), probe_origin("https://origin.example", opener=opener))
assert report["live_accept"] == "open"
assert "live_accept_closed" not in report["gates"]
assert report["ready_to_sit"] is False
assert "missing_act_binding" in report["gates"]

View file

@ -0,0 +1,146 @@
"""INFD-WP-0002-T03 sitting bind preflight.
Reports whether the compact sitting can be submitted to the live Stage 1
surface. Does not present, acknowledge, bind, or create approvals.
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.request import HTTPRedirectHandler, Request, build_opener, urlopen
from informed_decision.records import memo_from
ROOT = Path(__file__).resolve().parents[1] / "docs" / "batches" / "2026-09-14"
def load_sitting(root: Path = ROOT) -> dict:
sitting = json.loads((root / "sitting.json").read_text())
memos = []
for name in ("credentials", "decisions"):
index = json.loads((root / name / "index.json").read_text())
for row in index["ordinal"]:
memo = memo_from(json.loads((root / name / row["memo"]).read_text()))
memos.append(
{
"batch": name,
"memo_id": memo.id,
"approval_id": memo.approval_id,
"principal": memo.binding.principal.id,
"question": memo.question,
}
)
return {"sitting": sitting, "memos": memos}
class _NoRedirect(HTTPRedirectHandler):
def http_error_302(self, req, fp, code, msg, headers):
raise HTTPError(req.full_url, code, msg, headers, fp)
http_error_301 = http_error_303 = http_error_307 = http_error_308 = http_error_302
def _default_opener():
return build_opener(_NoRedirect()).open
def probe_origin(origin: str, opener=None) -> dict:
origin = origin.rstrip("/")
fetch = opener or _default_opener()
result = {"origin": origin, "healthz": None, "readyz": None, "auth_start": None}
def get(path):
req = Request(origin + path, method="GET")
try:
with fetch(req, timeout=10) as response:
return _probe_row(response, getattr(response, "status", None) or response.getcode())
except HTTPError as exc:
return _probe_row(exc, exc.code)
except (URLError, TimeoutError, OSError) as exc:
return {"http": None, "error": type(exc).__name__}
result["healthz"] = get("/healthz")
result["readyz"] = get("/readyz")
result["auth_start"] = get("/auth/start")
return result
def _probe_row(response, status):
headers = getattr(response, "headers", None)
location = headers.get("Location") if headers else None
raw = response.read(4096)
try:
body = json.loads(raw.decode("utf-8")) if raw else None
except (UnicodeDecodeError, json.JSONDecodeError):
body = None
row = {"http": status}
if location:
row["location_host"] = location.split("/")[2] if "://" in location else location
if isinstance(body, dict):
row["status"] = body.get("status")
if "reason" in body:
row["reason"] = body.get("reason")
return row
def evaluate(bundle: dict, origin_probe: dict | None = None) -> dict:
sitting = bundle["sitting"]
memos = bundle["memos"]
missing_act = [row["memo_id"] for row in memos if not row["approval_id"]]
pending_principal = [row["memo_id"] for row in memos if row["principal"] == "pending-human-session"]
gates = []
if sitting.get("status") != "draft-unsigned" or sitting.get("submitted") is True:
gates.append("sitting_already_submitted")
if len(memos) != 8:
gates.append("unexpected_memo_count")
if missing_act:
gates.append("missing_act_binding")
if pending_principal:
gates.append("principal_not_live_subject")
live_accept = None
if origin_probe is not None:
health = (origin_probe.get("healthz") or {}).get("http")
ready = (origin_probe.get("readyz") or {}).get("http")
start = (origin_probe.get("auth_start") or {}).get("http")
if health != 200:
gates.append("origin_healthz_not_ok")
if start not in (302, 303):
gates.append("auth_start_not_redirect")
if ready != 200:
gates.append("live_accept_closed")
live_accept = "closed"
else:
live_accept = "open"
ready_to_sit = not gates
return {
"kind": "informed-decision-sitting-preflight",
"sitting_id": sitting.get("id"),
"memo_count": len(memos),
"missing_act_binding": missing_act,
"principal_not_live_subject": pending_principal,
"live_accept": live_accept,
"gates": gates,
"ready_to_sit": ready_to_sit,
"agent_disposition": "forbidden",
"origin_probe": origin_probe,
}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--origin", help="Probe this Stage 1 origin; omit for file checks only")
parser.add_argument("--receipt", type=Path, help="Write the JSON report")
args = parser.parse_args()
probe = probe_origin(args.origin) if args.origin else None
report = evaluate(load_sitting(), probe)
text = json.dumps(report, indent=2, ensure_ascii=False) + "\n"
if args.receipt:
args.receipt.write_text(text, encoding="utf-8")
print(text, end="")
return 0 if report["ready_to_sit"] else 2
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -116,6 +116,21 @@ by a human through this surface, reconstructable from stored
presentation, and the blocking hub tasks are updated from those presentation, and the blocking hub tasks are updated from those
dispositions rather than from chat. dispositions rather than from chat.
2026-09-14 — **T08 bind path historically proven; this sitting still
cannot run.** Probe
`docs/evidence/2026-09-14-infd-0002-t03-bind-path-probe.json`.
The live store already holds three `accept` dispositions with confirmed
engine submissions for `SECRETS-WP-0010-T03-{apply,verify,exec}`
(presentations + required acks). That is not this batch. Compact memos
remain `approval_id: null` / `pending-human-session` and are not in the
store. New accept is refused: origin `/readyz` 503
`approval_path_not_connected` because `audit-core` is not Ready, its
Service has no ready endpoints, and the review pod gets connection
refused talking to the audit ClusterIP. Operator packet
`docs/batches/2026-09-14/OPERATOR.md`; preflight
`tools/sitting_bind_preflight.py`. No agent disposition. Task stays
`wait`.
## Feed outcomes back to State Hub without hub-authoring ## Feed outcomes back to State Hub without hub-authoring
```task ```task