diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/Makefile b/Makefile index f4c2ddf..15dcb55 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ PY := python3 TOOLS := tools -.PHONY: help register check checked due fixes coverage +.PHONY: help register check checked due fixes coverage test help: @echo "make register - rebuild REGISTER.md from findings/" @@ -15,18 +15,14 @@ register: @$(PY) $(TOOLS)/register_index.py check: - @$(PY) $(TOOLS)/register_index.py --check - @echo - @$(PY) $(TOOLS)/register_check.py - @echo - @$(PY) $(TOOLS)/inbox_check.py + @$(PY) $(TOOLS)/check_all.py checked: @$(PY) $(TOOLS)/record_check.py $(ARGS) @$(PY) $(TOOLS)/register_index.py due: - @$(PY) $(TOOLS)/register_check.py | sed -n '/Checks due/,/^$$/p' + @$(PY) $(TOOLS)/register_check.py @$(PY) $(TOOLS)/inbox_check.py fixes: @@ -34,3 +30,6 @@ fixes: coverage: @$(PY) $(TOOLS)/coverage.py + +test: + @PYTHONDONTWRITEBYTECODE=1 $(PY) -m unittest discover -s tests -v diff --git a/README.md b/README.md index 4219409..28b695c 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,19 @@ make check # verify the index, then report what is going quiet embargoes due for re-decision, escalations awaiting the operator, and what is owed at the production transition. It changes nothing. +All check stages run even when the index is stale; the command still exits +nonzero for a failed stage. `make due` shows the full obligation report and +inbox freshness, including full regulatory policies, embargo review deadlines +and publication handovers that remain pending after a finding closes. +`make checked` accepts finding, regulatory determination and full policy IDs; +it records an actual review, not a repair for an overdue warning. + +`make fixes` distinguishes owner workplans from recorded acceptance obligations. +`make check` and `make due` also show explicit `closure_condition` evidence still +owed by live findings, even when a source workplan has finished. Acceptance +terms and a finished workplan are not proof of runtime closure. + +Run `make test` for isolated regression checks. Python 3 and PyYAML are required. + - Intent: `INTENT.md` +- Current capability: [SCOPE.md](SCOPE.md) diff --git a/REGISTER.md b/REGISTER.md index 149ac01..268b19d 100644 --- a/REGISTER.md +++ b/REGISTER.md @@ -1,6 +1,6 @@ # Register -Generated by `tools/register_index.py` from `findings/`. Do not edit by hand. Last built 2026-09-02. +Generated by `tools/register_index.py` from `findings/`. Do not edit by hand. Last built 2026-09-05. 3 live of 11 findings; 3 notes below the floor. @@ -9,9 +9,9 @@ Generated by `tools/register_index.py` from `findings/`. Do not edit by hand. La | ID | Finding | System | Severity | Disclosure | Escalation | Fix owner | Status | Cadence | Next check | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | [RISK-F-0011](findings/RISK-F-0011-qonto-audit-deny-stream-completeness.md) | qonto-assistant audit.deny stream completeness is not established | qonto-assistant | medium | public | none | qonto-assistant | open | instant (0) | **due** | -| [RISK-F-0010](findings/RISK-F-0010-embedded-backup-webdav-credential.md) | Forgejo backup source embeds a WebDAV credential default | railiance-platform | low | embargoed | none | railiance-platform | open | 1h (1) | 2026-09-02 09:11Z | +| [RISK-F-0010](findings/RISK-F-0010-embedded-backup-webdav-credential.md) | Forgejo backup source embeds a WebDAV credential default | railiance-platform | low | embargoed | none | railiance-platform | open | instant (0) | **due** | | [RISK-F-0009](findings/RISK-F-0009-openbao-deny-set-covers-a-third-of-high-risk-lanes.md) | agent-high-risk-boundary denies 6 of 17 high-risk lanes; the direct bao path is unprotected for the rest | railiance-platform | **high** | public | none | railiance-platform | fixed | instant (0) | **due** | -| [RISK-F-0008](findings/RISK-F-0008-audit-retention-legal-basis-assumed.md) | The legal basis for retaining audit facts against an erasure request has been assumed, never established | audit-core | medium | public | **answered** (t2, answered) | risk-nexus | accepted | 1h (1) | 2026-09-02 09:11Z | +| [RISK-F-0008](findings/RISK-F-0008-audit-retention-legal-basis-assumed.md) | The legal basis for retaining audit facts against an erasure request has been assumed, never established | audit-core | medium | public | **answered** (t2, answered) | risk-nexus | accepted | 1h (1) | **due** | | [RISK-F-0007](findings/RISK-F-0007-unverified-tenant-boundary.md) | No consumer's tenant boundary is verified anywhere | estate | **high** | public | **answered** (t4, assigned) | per-consumer, on request | fixed | instant (0) | **due** | | [RISK-F-0006](findings/RISK-F-0006-apps-pg-no-backup-configured.md) | apps-pg has no backup configured at all: R0 means no recovery | railiance-platform | **high** | public | **answered** (t3, answered) | railiance-platform | fixed | instant (0) | **due** | | [RISK-F-0005](findings/RISK-F-0005-audit-core-unfiltered-read-path.md) | audit-core read path applies no tenant filter; the bound is deployment, not code | audit-core | medium | public | none | audit-core | fixed | instant (0) | **due** | @@ -35,8 +35,8 @@ Silence never buys a softer grade — see `docs/method/dependencies.md`. | Finding | Who | What would change | Default if silent | On | | --- | --- | --- | --- | --- | -| RISK-F-0011 | qonto-assistant | a published cadence plus a reconciliation view would let a later observation support completeness; a rejection keeps the grade and records that estate observation must not treat the stream as complete | the medium grade stands; missing cadence is recorded as a stalled remediation, and observation remains staffed with completeness pending | 2026-09-16 | -| RISK-F-0010 | railiance-platform | the finding becomes fixed and the embargo lifts | the low grade and embargo stand; missing fix tracking is recorded as a stalled remediation | 2026-09-15 | +| RISK-F-0011 | qonto-assistant | accepted deployed-instance evidence supports bounded stream completeness and permits closure; source-only evidence keeps the finding open | the medium grade stands; missing deployed acceptance is recorded as a stalled remediation, and observation remains staffed with completeness pending | 2026-09-16 | +| RISK-F-0010 | railiance-platform | the finding becomes fixed and the embargo lifts | the low grade and embargo stand; absent provider invalidation and recovery evidence is recorded as a stalled remediation | 2026-09-15 | | RISK-F-0008 | audit-core | a working keyed commitment narrows RISK-REG-0001 to retained-by-obligation categories only | encrypt-then-hash recorded as the only known route, and the retention period recorded as unstateable | 2026-11-17 | ## Embargoes diff --git a/SCOPE.md b/SCOPE.md new file mode 100644 index 0000000..31a2c10 --- /dev/null +++ b/SCOPE.md @@ -0,0 +1,71 @@ +# SCOPE — risk-nexus + +Updated 2026-09-05 (Europe/Berlin), including RISK-WP-0006 and RISK-WP-0007. This describes the checked-in capabilities; +[INTENT.md](INTENT.md) remains the intended remit. Evidence and remaining gaps +are in the [dated assessment](history/2026-09-05-014333-scope-intent-assessment.md). + +`risk-nexus` is a file-based risk register and regulatory reference collection, +owned by `the-custodian`, with Python command-line tools for indexing, review +bookkeeping and reports. Humans or agent sessions make the judgements and act +on the reports. This repository contains no web service, intake API, deployment +or independent scheduler, and does not itself serve `risk.coulomb.social`. + +## What it can do + +| Capability | Implemented scope and evidence | +| --- | --- | +| Record findings and notes | YAML front-matter and Markdown in `findings/` and `notes/`; [filing contract](findings/README.md) separates reporter facts from register grading. Research, review and observation have produced records. Incident intake is a written procedure; external-report contact provisioning remains routed elsewhere. | +| Grade and explain decisions | [Method instruments](docs/method/) define severity, disclosure, escalation, review, dependencies and verification. Dated rulings and finding histories record applied decisions. Scoring and acceptance are judgements, not computed decisions. | +| Generate a readable register | `make register` builds `REGISTER.md` from findings and notes, including constraints, finding waits, embargoes and cadence. It is an index, not the source of truth. | +| Report review work | `tools/register_check.py` reports due checks, missing grades, selected malformed fields, unknown statuses, waits/defaults, tracked fix state, embargo conditions and review dates, pending handovers and escalations, production re-score flags and regulatory reviews including full policies. `fixed` and `withdrawn` leave the ordinary watch list, but disclosure obligations remain visible; accepted, mitigated and unknown statuses remain watched. | +| Record review outcomes | `make checked ARGS=" clean\|moved\|defer ..."` updates an eligible record's timestamps, checker, cadence and review history, then regenerates the index. Clean climbs one rung from `instant` toward `1q`; moved resets; defer records a supplied date and reason. Completion of the review procedure and operator authorization for deferral are not enforced by the tool. | +| Read remediation records | `make fixes` resolves supported workplan/task IDs in live findings against sibling checkout files under `/home/worsch`, including an archive convention. It reports status, missing tracking and severity-based inactivity windows using the workplan file's last commit date. This is a proxy for recorded activity, not proof of task progress or a deployed fix. | +| Keep closure obligations visible | Live findings can state `closure_condition`, reported independently of source workplan completion. An accepted finding without fix tracking reports its recorded accepter, ending condition, resolvable regulatory determination and review date; missing fields still produce an incomplete-acceptance/missing-tracking warning. This does not verify that acceptance remains valid. | +| Check inbox freshness | `tools/inbox_check.py` reads up to 100 State Hub messages and matches names/IDs against live findings checked earlier. It reports potentially newer evidence; it neither interprets nor acknowledges messages. | +| Count reporting coverage | `make coverage` counts names appearing as system, fix owner or reporter and compares them with State Hub repo slugs when available. It measures mention coverage, not assessment coverage or assurance. | +| Retain regulatory research | One determination in `docs/regulatory/`, a thirteen-entry policy catalogue, and four full policy records. Sources, dates and activation conditions are stored for retrieval. Review discovery includes nested full policies; catalogues are excluded. All discovered records accept recorded outcomes. | +| Record disclosure and handover | Public, embargoed and restricted states express disclosure decisions. Finding metadata records publication status and addresses supplied by `policy-nexus`. Publication and access control are external; a `public` decision does not prove a completed handover. | +| Describe scheduled work | Two `activity-definitions/` files describe an hourly inbox watch and a daily 07:15 Europe/Berlin sweep. They ask an external activity system to instruct a session. They do not execute reviews or guarantee that a session acts. | + +## Operating dependencies and limits + +- Python 3, PyYAML, Make and Git are required for the relevant local commands; + no dependency manifest is checked in. `make test` runs isolated regression tests. +- State Hub reads use hard-coded `http://127.0.0.1:8000` endpoints. Fix tracking + assumes local sibling repositories and specific workplan naming/formatting. +- `make check` verifies the index and runs register and inbox reports even if a + prior stage fails, returning nonzero for any failed stage. Rendering includes the + current date and time-dependent due labels, so staleness need not mean source + records changed. `make due` runs the full obligation report and inbox check directly, + including nested policies, due embargo reviews and pending publication handovers + across closed as well as live findings. +- Reports do not send escalations, apply defaults, expire acceptances, lift + embargoes, close findings or block delivery. Operational warnings generally + return exit code zero. Selected validation exists, not comprehensive schema + enforcement; invalid date strings are tolerated by parsing and selected fields + are reported for repair. Deferrals require valid future dates. +- Incident deadlines and production-transition events + are procedural obligations without dedicated automated deadline/event handling. + The production-transition definition remains `proposed`. +- Historical verification records distinguish owner evidence, checkout evidence + and live reads. They do not establish current infrastructure access or health. + +## Current recorded footprint + +The checkout contains eleven findings: eight fixed, two open and one accepted; +three notes; one regulatory determination; four full policies out of thirteen +catalogue entries; and three verification records. Both open findings now carry +owner workplan tracking and explicit pending runtime/provider evidence. The +accepted finding carries a resolvable determination and an overdue substantive +review, displayed as a recorded acceptance obligation. Two findings are marked +published, eight pending handover, and one embargoed. These are repository +records, not a fresh verification of the affected systems or published sites. + +## Ownership boundary + +The repo owns risk intake records, grading, disclosure decisions, regulatory +research retention and escalation/review procedures. Fixes and incident response +belong to system owners; publication belongs to `policy-nexus`; canon and +estate-level decisions belong to the custodian/operator. It provides neither +legal advice nor compliance certification, estate-wide detection, or a delivery +gate. Completing the intended remit does not require taking over those roles. diff --git a/STATE.md b/STATE.md index 3fcc2d9..0c6fb84 100644 --- a/STATE.md +++ b/STATE.md @@ -1,6 +1,6 @@ # STATE — risk-nexus -**Updated:** 2026-09-02 +**Updated:** 2026-09-05 **Domain:** infotech · **Repo:** risk-nexus · **Owner:** the-custodian ## One-line posture @@ -20,13 +20,41 @@ backup credential, and an accepted medium on audit retention. | `RISK-WP-0003` | **finished** | Regulatory intake; the legal policy set | | `RISK-WP-0004` | **finished** | Running the register: cadence, verification, inbox-before-grading | | `RISK-WP-0005` | **finished** | The seven gaps from `history/2026-08-21-intent-gap-analysis.md` | +| `RISK-WP-0006` | **finished** | Reporting survives a stale index; full policies, embargo deadlines and closed-finding handovers stay visible; regression coverage and State Hub registration. | +| `RISK-WP-0007` | **finished** | Owner evidence reconciled, runtime/provider closure conditions recorded, and accepted obligations distinguished from missing engineering fixes. | + +## Scope correction and reporting repair — 2026-09-05 + +`SCOPE.md` now describes the checked-in capability. The timestamped assessment +in `history/2026-09-05-014333-scope-intent-assessment.md` records the remaining +gaps behind the earlier claim that WP-0005 closed them all. + +WP-0006 repairs the reporting path: `make check` runs every stage even after +failure; `make due` exposes regulatory policies and disclosure obligations; +malformed date strings no longer abort reporting; invalid deferrals cannot +write; archived completed workplans no longer trigger false inactivity alarms. +Eight regression tests pass. The live report now exposes four overdue policies, +eight pending publication handovers, and the existing overdue findings. +These are visibility repairs, not substantive reviews or evidence of closure. + +The daily activity source includes those obligations. Its live synchronization +and end-to-end delivery remain unverified. External intake, incident clocks, +production-transition decisions and estate assessment coverage remain open. + +WP-0007 then reconciled the owner evidence in RISK-V-0003. Qonto's source +cadence/reconciliation exists; KG-WP-0005-T03 waits for deployed acceptance. +RPF-WP-0029 removed the backup fallback; T02 waits for provider invalidation +and recovery receipts. Both findings were recorded as moved and remain open +at `instant`; F-0010 remains embargoed. F-0008's substantive review remains +overdue and its existing acceptance terms are displayed explicitly. Ten tests +pass. WP-0007 and four tasks are registered in State Hub. ## The register | ID | Sev | Status | Disclosure | Cadence | System | | --- | --- | --- | --- | --- | --- | | `RISK-F-0011` | medium | open | public | instant | qonto-assistant | -| `RISK-F-0010` | low | open | embargoed | 1h | railiance-platform | +| `RISK-F-0010` | low | open | embargoed | instant | railiance-platform | | `RISK-F-0009` | high | fixed | public | instant | railiance-platform | | `RISK-F-0008` | medium | accepted | public | 1h | audit-core | | `RISK-F-0007` | high | fixed | public | instant | estate | @@ -76,8 +104,8 @@ outcome. | Who | On | Defaults | | --- | --- | --- | -| qonto-assistant | heartbeat / emission-cadence and a reconciliation view for `audit.deny` (`F-0011`) | 2026-09-16 | -| railiance-platform | revoke or invalidate the provider credential, remove the source default, name fix tracking (`F-0010`) | 2026-09-15 | +| qonto-assistant | deployed-instance capture and King's Guard acceptance under `KG-WP-0005-T03`; source cadence/reconciliation now exist (`F-0011`) | 2026-09-16 | +| railiance-platform | provider invalidation and recovery receipts under `RPF-WP-0029-T02`; source fallback removed (`F-0010`) | 2026-09-15 | | the-custodian | canon kinds packet | 2026-09-17 | | audit-core | keyed commitment; `platform-pg` co-residency horizon | 2026-11-17 | @@ -107,10 +135,10 @@ statehub fix-consistency --repo risk-nexus ## Known conditions -- **Workplans are not indexed in the hub.** C-06 on all four: this instance is +- **Earlier workplans were not indexed in the hub.** C-06 on the earlier set: this instance was not the identifier registrar. Needs a run with `STATEHUB_REGISTRAR=1` on the - production instance. The estate's read model cannot see any `risk-nexus` - workplan. + production instance. WP-0006 now has a registered Hub workplan and five task + UUIDs; this implementation did not repair historical workplan registration. - **C-31** fires on `RISK-F-` ids until canon registers the kinds. Packet sent. Defaults 2026-09-17. - **OpenBao is unverifiable from here** (403). Every grade touching an OpenBao diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index bc90514..196af51 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -13,6 +13,8 @@ | workplan | RISK-WP-0003 | finished | — | workplans/RISK-WP-0003-regulatory-intake.md | | workplan | RISK-WP-0004 | finished | — | workplans/RISK-WP-0004-run-the-register.md | | workplan | RISK-WP-0005 | finished | — | workplans/RISK-WP-0005-close-the-intent-gaps.md | +| workplan | RISK-WP-0006 | finished | — | workplans/RISK-WP-0006-keep-review-obligations-visible.md | +| workplan | RISK-WP-0007 | finished | — | workplans/RISK-WP-0007-reconcile-owner-evidence.md | | task | RISK-WP-0001-T01 | done | — | workplans/RISK-WP-0001-make-the-register-decidable.md | | task | RISK-WP-0001-T02 | done | — | workplans/RISK-WP-0001-make-the-register-decidable.md | | task | RISK-WP-0001-T03 | done | — | workplans/RISK-WP-0001-make-the-register-decidable.md | @@ -41,3 +43,12 @@ | task | RISK-WP-0005-T05 | done | — | workplans/RISK-WP-0005-close-the-intent-gaps.md | | task | RISK-WP-0005-T06 | done | — | workplans/RISK-WP-0005-close-the-intent-gaps.md | | task | RISK-WP-0005-T07 | done | — | workplans/RISK-WP-0005-close-the-intent-gaps.md | +| task | RISK-WP-0006-T01 | done | — | workplans/RISK-WP-0006-keep-review-obligations-visible.md | +| task | RISK-WP-0006-T02 | done | — | workplans/RISK-WP-0006-keep-review-obligations-visible.md | +| task | RISK-WP-0006-T03 | done | — | workplans/RISK-WP-0006-keep-review-obligations-visible.md | +| task | RISK-WP-0006-T04 | done | — | workplans/RISK-WP-0006-keep-review-obligations-visible.md | +| task | RISK-WP-0006-T05 | done | — | workplans/RISK-WP-0006-keep-review-obligations-visible.md | +| task | RISK-WP-0007-T01 | done | — | workplans/RISK-WP-0007-reconcile-owner-evidence.md | +| task | RISK-WP-0007-T02 | done | — | workplans/RISK-WP-0007-reconcile-owner-evidence.md | +| task | RISK-WP-0007-T03 | done | — | workplans/RISK-WP-0007-reconcile-owner-evidence.md | +| task | RISK-WP-0007-T04 | done | — | workplans/RISK-WP-0007-reconcile-owner-evidence.md | diff --git a/activity-definitions/daily-register-check-sweep.md b/activity-definitions/daily-register-check-sweep.md index 59344eb..18fca33 100644 --- a/activity-definitions/daily-register-check-sweep.md +++ b/activity-definitions/daily-register-check-sweep.md @@ -77,9 +77,12 @@ condition: 'true' action: task_template: 'risk-nexus: work the register due list' description: >- - Run `make due` in risk-nexus, then work each listed finding through the five + Run `make due` in risk-nexus, then work each due finding through the five questions in docs/method/check-procedure.md and record the outcome with - `make checked`. Findings at the `instant` rung are due immediately; the + `make checked`. Also review due regulatory records and full policies, + re-decide due embargoes, and reconcile pending publication handovers, + including closed findings, per docs/method/check-procedure.md. + Findings at the `instant` rung are due immediately; the ladder climbs only on a clean check. Escalate only what docs/method/escalation.md says to escalate, batched into one operator conversation. If nothing is due, that is a complete run and worth nothing diff --git a/docs/method/check-procedure.md b/docs/method/check-procedure.md index efc04c6..0260d9b 100644 --- a/docs/method/check-procedure.md +++ b/docs/method/check-procedure.md @@ -40,6 +40,18 @@ Then, per due finding: be met while the embargo still holds, if a sibling finding would be exposed by publishing (`RISK-F-0003`). +Also work the non-finding obligations printed by `make due`: review the sources +and activation context for each due regulatory determination or full policy; +re-decide due embargoes; and reconcile pending publication handovers with the +publication owner's evidence, including for closed findings. Regulatory reviews +use `make checked ARGS=" clean|moved ..."` after the +review. A handover report is not authorization to publish embargoed content. + +Index validation failure does not suppress later `make check` stages. Read the +reports before regenerating the index. A successful regeneration is not a +review outcome. Invalid date fields remain work to repair rather than evidence +that a check is current. + Then record it, in the same sitting: ``` diff --git a/docs/verifications/2026-09-05-open-findings-source-review.md b/docs/verifications/2026-09-05-open-findings-source-review.md new file mode 100644 index 0000000..c7ab5bd --- /dev/null +++ b/docs/verifications/2026-09-05-open-findings-source-review.md @@ -0,0 +1,104 @@ +--- +id: RISK-V-0003 +type: verification +title: "Owner-source progress and outstanding runtime evidence" +date: "2026-09-05" +owner: risk-nexus +workplan: RISK-WP-0007 +findings: [RISK-F-0011, RISK-F-0010, RISK-F-0008] +--- + +# Owner-source review — 2026-09-05 + +This is a checkout and inbox review, not a runtime probe. No credentials were +used or reproduced, no system was changed and no legal determination was renewed. +Files can include uncommitted owner work; repository HEAD is context, not proof +that every inspected change is in that commit. + +## Qonto deny-stream completeness + +Read State Hub notice `c6442eef-34fc-46a7-9639-10f2cd6120fc` (2026-09-04), +QONTO-WP-0005, `specs/audit-emission-cadence.yaml`, `src/qonto_assistant/audit.py`, +the application lifespan/reconciliation endpoint, and KG-WP-0005. + +The source now declares process instance/sequence semantics, a 24-hour default +active-process heartbeat, startup and best-effort shutdown, and reconciliation +counts. AuditLogger serializes emission and exposes source counters; the app +wires the heartbeat lifecycle and identity-checked snapshot route. Qonto records +88 passing tests on its latest review; that suite was not rerun by risk-nexus. +King's Guard records local source-path validation and explicitly leaves +KG-WP-0005-T03 waiting for authorized deployed evidence. + +**Conclusion:** the old statement that no source cadence/reconciliation exists +is obsolete. Runtime completeness remains unestablished. F-0011 stays open, +medium and public, with QONTO-WP-0005 and KG-WP-0005-T03 as tracking. + +**Closure evidence owed:** qonto's runtime owner supplies a bounded capture from +one deployed instance, naming the deployed revision, configured heartbeat +interval and observation window. It covers startup, a request transition, +periodic heartbeat timing, sequence/instance continuity and a same-instance +reconciliation snapshot. King's Guard compares received counts and source counts, +records any gaps and the acceptance decision. Evidence may support only the +observed instance/window, never an unbounded all-time completeness claim. +The original 2026-09-16 default remains; a completed source plan does not close +the runtime finding. This narrows the existing evidence request; no new request +or message was sent in this sitting. + +## Backup credential + +Read RPF-WP-0029 and inspected `tools/cmd/forgejo-backup` in memory for the +named shell fallback without outputting credential-bearing lines. No nonempty +literal fallback for `RAILIANCE_BACKUP_NC_TOKEN` remains in that script. +The owner records fail-closed input tests as complete under T01. T02 is `wait`: +provider-side invalidation and encrypted upload/restore receipts are absent. + +**Conclusion:** source removal is established in the checkout; predecessor +invalidation is not. F-0010 stays open, low and embargoed. RPF-WP-0029-T02 names +the remaining provider/recovery work. The 2026-09-15 embargo review/default +stands. No inference is made about whether the predecessor is still valid. +Only non-secret invalidation, governed ciphertext upload and restore receipts +can complete the remaining evidence; source removal alone cannot lift the hold. + +## Accepted retention obligation + +Read the existing acceptance and determination references in F-0008 and +`audit-core/docs/erasure-and-audit.md`. Audit Core still documents the +cleartext-hash confirmation problem and says keyed commitments are not built. +No new answer to the co-residency horizon or keyed-commitment wait was present +in the fetched risk-nexus inbox. This is not proof that no answer exists elsewhere. + +F-0008 already has a named accepter, an ending condition, a real determination +and a next check. The reporting problem is classifying that accepted obligation +as an untracked fix. The report should expose its existing acceptance terms +without inventing engineering work or treating acceptance as closure. Its +substantive review remains due: this sitting does not establish that the +real-person/counterparty trigger has not fired and does not renew the legal basis. + +## External intake and scheduled reviews + +Searched policy-nexus publication configuration, workplans and deployment files +for a security contact/security.txt or delivered external-report route. None +was found in those inspected sources. The existing intake proposal remains the +available evidence; current public endpoints and estate-wide contact availability +were not verified. Publishing a new contact requires its actual receiving owner. + +Searched activity-core's local definitions and evidence for the two risk register +activities. No matching execution receipt was found in those paths. The two +definitions in risk-nexus remain source instructions. Their live registration, +execution and completed-session chain are unverified; this search is not proof +that the activities do not run. No deployment or synchronization was attempted. + +## Source provenance + +| Inspected source | SHA-256 of the non-secret document | +| --- | --- | +| qonto-assistant QONTO-WP-0005 | `f3b21ed7d046e7a4e5e8eee1397dcdcf1a17211f75786da3a0a2258b44802c3a` | +| qonto-assistant cadence declaration | `69db6068b2e262f6b9108903cf7c5a7764efba5c981344c50df496018bc98ed9` | +| qonto-assistant audit.py | `573e669914ae2ae77dfbf0a0929ef080cfbcb7dd4e0ba79e9408dd401fd74437` | +| railiance-platform RPF-WP-0029 | `396d2e167b8bd1141671fadd864660a1e8bc6efb1ea3a09be3465e841ce33144` | +| audit-core erasure-and-audit.md | `40d0f86ad401c3fb413fb21346a77210b6840d52f05093b31143a11b8a3a878b` | + +Observed HEADs: qonto `e5611147fd72a23e58618b87b0c3b96c807d0043`, +railiance-platform `f637989a6911a7a13812cba223ffc007d78d6302`, +audit-core `95dcb78e17d46889c17b55e3ba1aadbcceec6a99`, +policy-nexus `c1b60f322e3bcddff3cece618ece2bd3686169e1`. diff --git a/findings/RISK-F-0008-audit-retention-legal-basis-assumed.md b/findings/RISK-F-0008-audit-retention-legal-basis-assumed.md index c23a70b..7995488 100644 --- a/findings/RISK-F-0008-audit-retention-legal-basis-assumed.md +++ b/findings/RISK-F-0008-audit-retention-legal-basis-assumed.md @@ -365,3 +365,17 @@ achievable. An accepted risk still gets checked. - **2026-08-20** — not clean: Trigger list ruled: no external determination in build mode; accepted with the legal policy set as the compensating control. Cadence instant → instant; checked again immediately. - **2026-09-01** — not clean: the regulatory record now states target periods per category; the remaining gap is whether platform-pg co-residency can achieve them, while the keyed-commitment question is unchanged. Grade and acceptance hold. Cadence instant → instant; checked again immediately. - **2026-09-02** — clean check: acceptance, RISK-REG-0001, and the audit-core keyed-commitment wait unchanged. Cadence instant → 1h (1 clean in a row); next check 2026-09-02 09:11Z. + +## Evidence inspection — 2026-09-05 (review remains due) + +[RISK-V-0003](../docs/verifications/2026-09-05-open-findings-source-review.md) +records a source/inbox inspection. The technical owner still documents the +cleartext-hash limitation; no new answer to the existing wait appeared in the +fetched risk-nexus inbox. The report now displays this record's existing +accepter, ending condition, determination and review date as a recorded +acceptance obligation, rather than inventing a fix plan to fill `fix_tracking`. + +This is not a clean check or a renewed acceptance. Whether the real-person or +counterparty condition has fired, and the substantive regulatory review, remain +unestablished here. The overdue review, existing grade and acceptance terms are +preserved; absence of a new inbox answer cannot certify the condition false. diff --git a/findings/RISK-F-0010-embedded-backup-webdav-credential.md b/findings/RISK-F-0010-embedded-backup-webdav-credential.md index f1ae890..492013c 100644 --- a/findings/RISK-F-0010-embedded-backup-webdav-credential.md +++ b/findings/RISK-F-0010-embedded-backup-webdav-credential.md @@ -12,7 +12,9 @@ date_filed: "2026-08-23" system: railiance-platform environment: production fix_owner: railiance-platform -fix_tracking: unset +fix_tracking: RPF-WP-0029-T02 +closure_condition: "provider invalidation plus governed ciphertext upload and restore receipts; source fallback removal alone does not lift the embargo" +verification: RISK-V-0003 # Graded by risk-nexus 2026-09-01 — docs/rulings/2026-09-01-inbox-sweep.md severity: low severity_at_production: low @@ -25,20 +27,20 @@ embargo_condition: "the provider credential is revoked or invalidated and the li embargo_since: "2026-09-01" embargo_review: "2026-09-15" escalation: none -last_checked: "2026-09-02T08:11:17Z" -next_check: "2026-09-02T09:11:17Z" -cadence: 1h -clean_streak: 1 +last_checked: "2026-09-05T00:05:51Z" +next_check: "2026-09-05T00:05:51Z" +cadence: instant +clean_streak: 0 waiting_on: - who: railiance-platform - what: "revoke or invalidate the provider credential, remove the source default, name fix tracking, and demonstrate governed ciphertext upload plus restore" + what: "complete RPF-WP-0029-T02: revoke or invalidate the provider credential and demonstrate governed ciphertext upload plus restore; source fallback removal is established" since: "2026-09-01" would_change: "the finding becomes fixed and the embargo lifts" - default: "the low grade and embargo stand; missing fix tracking is recorded as a stalled remediation" + default: "the low grade and embargo stand; absent provider invalidation and recovery evidence is recorded as a stalled remediation" default_at: "2026-09-15" graded_by: risk-nexus ruling: RISK-RULING-2026-09-01-A -checked_by: "grok/risk-nexus" +checked_by: "codex/risk-nexus" --- # RISK-F-0010 — Forgejo backup source embeds a WebDAV credential default @@ -118,7 +120,21 @@ does not soften the assessment. Reasoning: `docs/rulings/2026-09-01-inbox-sweep.md`. +## Source remediation reconciled — 2026-09-05 + +RPF-WP-0029-T01 records source fallback removal and fail-closed input checks. +This register confirmed the named fallback is absent in the current script +without displaying or testing any credential. RPF-WP-0029-T02 remains `wait` +for provider invalidation and governed encrypted upload/restore receipts. + +[RISK-V-0003](../docs/verifications/2026-09-05-open-findings-source-review.md) +records the boundary: source removal does not establish invalidation of the +predecessor. Open, low, embargoed and no escalation remain appropriate on the +available evidence. The 2026-09-15 review/default is unchanged. The historical +source-default statements above describe the earlier assessments. + ## Reviews - **2026-09-01** — graded from the filed report and a redacted current-source check. The literal default remains; no fix record was found. Cadence starts at instant. - **2026-09-02** — clean check: literal source default remains; no fix tracking; embargo and 2026-09-15 wait unchanged. Cadence instant → 1h (1 clean in a row); next check 2026-09-02 09:11Z. +- **2026-09-05** — not clean: Confirmed source fallback removal and RPF-WP-0029 tracking; provider invalidation and encrypted upload/restore receipts remain pending. Low grade and embargo retained; see RISK-V-0003. Cadence 1h → instant; checked again immediately. diff --git a/findings/RISK-F-0011-qonto-audit-deny-stream-completeness.md b/findings/RISK-F-0011-qonto-audit-deny-stream-completeness.md index 0e457a4..4bb3078 100644 --- a/findings/RISK-F-0011-qonto-audit-deny-stream-completeness.md +++ b/findings/RISK-F-0011-qonto-audit-deny-stream-completeness.md @@ -12,7 +12,9 @@ date_filed: "2026-09-02" system: qonto-assistant environment: production fix_owner: qonto-assistant -fix_tracking: unset +fix_tracking: QONTO-WP-0005 / KG-WP-0005-T03 +closure_condition: "authorized deployed-instance capture accepted by kings-guard: heartbeat timing, sequence continuity and same-instance reconciliation" +verification: RISK-V-0003 # Graded by risk-nexus 2026-09-02 — docs/rulings/2026-09-02-qonto-deny-stream.md severity: medium severity_at_production: medium @@ -29,20 +31,20 @@ revision: "graded-1" last_reviewed: "2026-09-02" review_interval: 6m escalation: none -last_checked: "2026-09-02T07:20:00Z" -next_check: "2026-09-02T07:20:00Z" +last_checked: "2026-09-05T00:05:51Z" +next_check: "2026-09-05T00:05:51Z" cadence: instant clean_streak: 0 waiting_on: - who: qonto-assistant - what: "publish a heartbeat or emission-cadence declaration and a reconciliation view for the audit.deny stream, or reject that obligation" + what: "supply runtime-owner deployed-instance capture for kings-guard acceptance under KG-WP-0005-T03; source cadence and reconciliation now exist" since: "2026-09-02" - would_change: "a published cadence plus a reconciliation view would let a later observation support completeness; a rejection keeps the grade and records that estate observation must not treat the stream as complete" - default: "the medium grade stands; missing cadence is recorded as a stalled remediation, and observation remains staffed with completeness pending" + would_change: "accepted deployed-instance evidence supports bounded stream completeness and permits closure; source-only evidence keeps the finding open" + default: "the medium grade stands; missing deployed acceptance is recorded as a stalled remediation, and observation remains staffed with completeness pending" default_at: "2026-09-16" graded_by: risk-nexus ruling: RISK-RULING-2026-09-02-A -checked_by: "grok/risk-nexus" +checked_by: "codex/risk-nexus" --- # RISK-F-0011 — qonto-assistant audit.deny stream completeness is not established @@ -141,6 +143,22 @@ wait makes that last statement expire. Reasoning: `docs/rulings/2026-09-02-qonto-deny-stream.md`. +## Source remediation reconciled — 2026-09-05 + +The missing-source statements above describe intake, not the current checkout. +QONTO-WP-0005 is finished: cadence, heartbeat lifecycle, instance/sequence fields +and an identity-checked reconciliation view now exist. King's Guard's local +source-path checks are recorded complete, but KG-WP-0005-T03 waits for an +authorized deployed capture. Notice `c6442eef-34fc-46a7-9639-10f2cd6120fc` +explicitly requests independent deployed-stream acceptance before closure. + +[RISK-V-0003](../docs/verifications/2026-09-05-open-findings-source-review.md) +records the inspected evidence and bounded acceptance requirements. Status +remains open, medium, public, no escalation: deployed completeness is still +unknown, with no evidence of suppression or an authorization bypass. The +2026-09-16 default is retained, now describing the actual remaining obligation. + ## Reviews - **2026-09-02** — graded from the Gate House intake, the named conformance review, and a current-source check of qonto-assistant. Local lockout is in-process; the emitted deny stream still has no cadence or reconciliation view. Cadence starts at instant. +- **2026-09-05** — not clean: Reviewed owner notice, cadence/logger/endpoint and KG-WP-0005; source remediation exists, deployed-instance acceptance remains pending under KG-WP-0005-T03. See RISK-V-0003. Cadence instant → instant; checked again immediately. diff --git a/history/2026-09-05-014333-scope-intent-assessment.md b/history/2026-09-05-014333-scope-intent-assessment.md new file mode 100644 index 0000000..9dc82b4 --- /dev/null +++ b/history/2026-09-05-014333-scope-intent-assessment.md @@ -0,0 +1,192 @@ +--- +type: history +title: "Repository scope against INTENT: capabilities and remaining gaps" +date: "2026-09-05" +assessed_at: "2026-09-05T01:43:33+02:00" +author: codex +repo: risk-nexus +source_revision: "29f50d5143afc8c9d9884810dc34e39adf6edd43" +--- + +# Scope against intent + +The repository implements a usable, manually operated risk register with +reporting aids. It does not yet deliver the full intended assurance that known +risks cannot go quiet, regulation stays current, or reports can arrive from any +source. [SCOPE.md](../SCOPE.md) was absent and has been created to describe the +actual capability and its dependencies. + +This assessment reads the local implementation, records and workplans, and runs +read-only reports. It does not re-grade findings, verify external fixes, check +publication endpoints, or reassess the legal correctness of regulatory texts. +External operational claims below are identified as recorded or unverified. +The timestamp prefix uses Europe/Berlin; the inspection timestamp is +2026-09-04 23:43:33 UTC. The existing modified Python bytecode file was left alone. + +## INTENT ownership claims + +| INTENT claim | Assessment | Evidence and boundary | +| --- | --- | --- | +| Finding intake from research, review, incident and external report | Partial | [Filing contract](../findings/README.md), eleven finding files and observation-sourced F-0011 demonstrate internal intake. [Incident/external method](../docs/method/intake.md) supplies a procedure but records the external contact as only routed. No intake service or incident clock is implemented here. | +| Severity and triage | Implemented as a judgement workflow | [Severity method](../docs/method/severity.md), dated rulings and all three live records carry grades. Tools surface absent grades but do not compute or independently verify them. | +| Remediation tracking: owner, state and stalls | Partial | [Fix tracker](../tools/fix_tracker.py) reads supported owner workplans and flags inactivity. All three live findings currently report no fix tracking; local workplan commits are only a progress proxy. | +| Disclosure timing and deliberate holds | Implemented records; partial follow-through | [Disclosure method](../docs/method/disclosure.md) and F-0010 record a conditional embargo. Public/pending/published metadata distinguishes decisions from delivery, but embargo review deadlines and pending publication are not actively checked. | +| Regulatory intake and retained research | Partial | [Regulatory collection](../docs/regulatory/README.md) retains one determination; the [policy catalogue](../docs/regulatory/policies/README.md) names thirteen positions, four written in full. Policy review dates are outside tool discovery. | +| Written escalation rule and operator attention | Rule implemented; delivery procedural | [Escalation method](../docs/method/escalation.md) defines triggers and delivery states. The checker prints pending and seven-day-unacknowledged items; it does not send, retry, acknowledge or apply defaults. | + +The intended boundaries are substantially preserved: no remediation service, +publication host, legal-advice service or canon authority has been added. +Verification is bounded by the [verification method](../docs/method/verification.md). +There is a wording conflict to resolve: the adopted review method says re-scoring +occurs before a production declaration completes, while INTENT excludes delivery +blocking and the proposed transition method says it does not gate the event. +No delivery gate is implemented. + +## Gaps and concrete completion criteria + +### 1. The review entry point can suppress the overdue reports + +**Priority: high. Owner: risk-nexus.** `make check` fails on stale `REGISTER.md` +before running the register or inbox checker. The index embeds today's date and +time-dependent due labels ([generator](../tools/register_index.py)), so the +passage of time alone can stop the operational report. The direct checker +currently reports all three live findings overdue and a two-day heartbeat gap. + +Separate index validation from report execution so a stale index still exposes +due work, heartbeat and inbox evidence. Completion means the stale-index case +reports both the failure and the operational work list. Actual overdue reviews +must be performed with evidence; regenerating the index cannot satisfy them. + +### 2. Regulatory policies can silently expire outside the review loop + +**Priority: high. Owner: risk-nexus.** `register_lib.regulatory()` uses a +non-recursive `docs/regulatory/*.md` scan. All four full policies are nested in +`policies/`, have `next_check: 2026-08-20T21:30:00Z`, and are omitted from both +the due report and `record_check.py`'s eligible records. Method review intervals +likewise have no general due-date reporting. This contradicts INTENT's retained, +non-silently-expiring regulatory knowledge requirement. + +Discover and review the full policy records, make them eligible for recorded +outcomes, and define how method review dates are maintained. Distinguish the +nine catalogue-only entries from sourced determinations, with owners and dates +for elaboration when activation approaches. Completion means an overdue nested +policy appears in the work list and can receive a recorded review. + +### 3. External and incident intake remain incomplete in operation + +**Priority: high. Owners: risk-nexus; policy-nexus and the-custodian for contact delivery.** +The intake method still says no external contact exists and describes a routed +proposal. Local evidence establishes neither a delivered contact nor a tested +external-to-register path; it does not establish the current state of the whole +estate. Incident fields and urgent timing are prose only: the tools do not +process `incident` or `first_observed`, enforce incident cadence, or report the +notification deadline described by the method. No incident-marked finding was +found in this checkout. + +Obtain evidence of a discoverable contact and a report reaching triage. Add +deadline handling for incident records and demonstrate an urgent intake exercise +with an owner and escalation record. The legal basis for any deadline remains +subject to the regulatory record's own review; this assessment does not validate it. + +### 4. Remediation visibility is incomplete for the live register + +**Priority: high. Owners: risk-nexus and the named system owners.** F-0010, +F-0011 and accepted F-0008 all have unset tracking. The inbox check finds a newer +F-0011 remediation notice naming QONTO-WP-0005, so the register is demonstrably +behind incoming evidence. The notice is not proof the defect is fixed. + +Read and reconcile that evidence, obtain the applicable tracking records, and +record an explicit reviewable acceptance obligation where a fix plan is not +appropriate. The tracker resolves only a workplan ID syntax and expected file +layout, uses whole-file commit time rather than task transitions, and does not +verify deployed state. Completion means every live finding has an actionable +tracking or acceptance record and inactivity remains visible independently of +register check frequency. Task shorthand and archived completed-status handling +also need care: the parser requires full IDs, and the appended `[archived]` +suffix prevents the current exact completed-state comparison from matching. + +### 5. Expiry, escalation and execution still depend on an attending session + +**Priority: high. Owners: risk-nexus; activity-core for execution evidence.** +The tools report lateness and suggested actions but do not apply them. A heartbeat +printed by the same command nobody runs cannot provide independent delivery +assurance. The two activity definitions request work; their presence does not +prove scheduler registration, execution, session completion or operator receipt. +The observed overdue records show the intended cadence is not currently reflected +in recorded outcomes. No escalation is presently reported as awaiting an answer. + +Demonstrate scheduled instruction delivery through to a completed review, and an +independently visible missed-run condition. Demonstrate acknowledgement and the +single permitted re-raise/default path for an escalation. Human judgement should +remain required; reliable delivery need not automate grading or send repeated nags. + +### 6. Disclosure and production obligations are recorded but not fully followed + +**Priority: medium. Owners: risk-nexus, policy-nexus and the-custodian.** +The checker lists embargo conditions but never compares `embargo_review` with +the clock. It does not list pending publication handovers; eight findings have +that state, including fixed records outside the normal live watch list. Two +findings are marked published; those metadata were not verified against a site. + +The [production definition](../docs/method/production-transition.md) remains +`proposed`, contains historical counts and refers to F-0007's former acceptance +although that finding is now fixed. The checker reports only live flagged +findings (currently F-0008), not an event-driven acceptance expiry or policy +activation process. + +Completion means due embargo decisions and pending handovers remain actionable +independently of closure; the custodian settles the transition definition and +conflicting declaration wording; an event record identifies the applicable +re-scores, acceptance decisions and policy activations without silently adding a gate. + +### 7. Coverage measures mentions, not whether systems have been assessed + +**Priority: medium. Owners: risk-nexus with estate inventory owners.** +[coverage.py](../tools/coverage.py) combines system, owner and reporter strings, +including values such as `estate` and `per-consumer, on request`. It has no +last-assessed date, assessment scope or evidence for a clean assessment. Thus a +mentioned repo is not necessarily assessed, and an unmentioned repo is unknown. +The current inventory request did not yield a denominator; the historical +7-of-117 count must not be presented as current coverage. + +Record assessment provenance and dates against canonical repo identities, with +explicit unknowns. Completion means a clean assessment, a reported defect and +an unassessed system can be distinguished without turning this repo into the +estate's scanner or remediation team. + +## What good looks like: current result + +- **Known gaps have severity and owner:** met for the recorded live findings; + completeness across the estate is unproven. +- **Applicable regulation is found before needed:** not established. The + [previous assessment](2026-08-21-intent-gap-analysis.md) records retrospective + discovery of already-live obligations. A catalogue improves retrieval but + does not erase that history or provide ongoing horizon scanning. +- **Stalled fixes are visible while stalled:** partially supported by a local + file reader, presently limited by missing tracking and overdue reviews. +- **Necessary operator judgement is reached without noise:** procedures and + historical rulings exist; current end-to-end delivery assurance is unverified. +- **Findings expire or escalate; the register stays readable:** the small + register and note floor are implemented. Timely follow-through remains a + process requirement rather than a guaranteed outcome. + +[WP-0005](../workplans/RISK-WP-0005-close-the-intent-gaps.md) and STATE.md call the +previous seven gaps closed. The delivered tools and procedures are real progress, +but routing a contact request, drafting a transition definition, printing a +heartbeat and counting mentions do not establish those end-to-end outcomes. +Historical completion records were preserved; this assessment records the +remaining capability gaps rather than rewriting their history. + +## Verification results + +| Read-only check | Observed result | +| --- | --- | +| `PYTHONDONTWRITEBYTECODE=1 make check` | Failed at stale index, Make exit 2; subsequent report stages did not execute. | +| `PYTHONDONTWRITEBYTECODE=1 python3 tools/register_check.py` | Exit 0; three live findings, all overdue by about 62–64 hours; two-day heartbeat warning; three missing fix trackers; one overdue top-level regulatory record; no pending operator escalations. | +| `PYTHONDONTWRITEBYTECODE=1 make fixes` | Exit 0; all three live findings report `NO FIX TRACKING`. | +| `PYTHONDONTWRITEBYTECODE=1 python3 tools/inbox_check.py` | Exit 0; nine messages returned, including a newer F-0011 remediation notice dated 2026-09-04. No messages were acknowledged or sent. | +| `PYTHONDONTWRITEBYTECODE=1 make coverage` | Exit 0; eleven distinct mention strings; inventory endpoint unavailable within the tool's attempts, so current denominator unknown. The separate inbox request succeeded. | + +Warnings returning zero are advisory output, not a clean bill of health. The +review tools were run directly after the stale-index failure to inspect their +results without regenerating unrelated files or claiming reviews had happened. diff --git a/tests/test_review_workflow.py b/tests/test_review_workflow.py new file mode 100644 index 0000000..25fec16 --- /dev/null +++ b/tests/test_review_workflow.py @@ -0,0 +1,157 @@ +import contextlib +import datetime as dt +import io +import pathlib +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "tools")) +import check_all +import fix_tracker +import record_check +import register_check +import register_lib as lib + +NOW = dt.datetime(2026, 9, 5, tzinfo=dt.timezone.utc) + + +class ReviewWorkflowTests(unittest.TestCase): + def report(self, findings=(), regulatory=()): + output = io.StringIO() + with patch.object(lib, "findings", return_value=list(findings)), \ + patch.object(lib, "regulatory", return_value=list(regulatory)), \ + patch.object(lib, "records_with_waits", return_value=[]), \ + patch.object(fix_tracker, "report", return_value=[]), \ + patch.object(register_check, "NOW", NOW), contextlib.redirect_stdout(output): + self.assertEqual(register_check.main(), 0) + return output.getvalue() + + def test_all_stages_run_after_failure(self): + for failed_stage in range(3): + results = [subprocess.CompletedProcess([], int(i == failed_stage)) for i in range(3)] + with patch.object(check_all.subprocess, "run", side_effect=results) as run, \ + contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(check_all.main(), 1) + self.assertEqual(run.call_count, 3) + self.assertTrue(run.call_args_list[-1].args[0][1].endswith("inbox_check.py")) + + def test_all_success_and_spawn_failure(self): + with patch.object(check_all.subprocess, "run", return_value=subprocess.CompletedProcess([], 0)), \ + contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(check_all.main(), 0) + with patch.object(check_all.subprocess, "run", side_effect=[OSError("unavailable"), + subprocess.CompletedProcess([], 0), subprocess.CompletedProcess([], 0)]) as run, \ + contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(check_all.main(), 1) + self.assertEqual(run.call_count, 3) + + def test_nested_policy_discovered_reported_and_reviewable(self): + with tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + policies = root / "docs/regulatory/policies" + policies.mkdir(parents=True) + (policies / "README.md").write_text("Catalogue without front-matter") + policy = policies / "example.md" + policy.write_text('---\nid: RISK-POL-9999\nnext_check: "2026-08-20"\n' + 'cadence: instant\n---\n\n# Policy\n') + with patch.object(lib, "REPO", root): + records = lib.regulatory() + self.assertEqual([r["id"] for r in records], ["RISK-POL-9999"]) + self.assertIn("RISK-POL-9999 — due", self.report(regulatory=records)) + with patch.object(lib, "findings", return_value=[]), patch.object(lib, "now", return_value=NOW), \ + contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(record_check.main(["RISK-POL-9999", "clean", "review evidence"]), 0) + reviewed = lib.load(policy) + self.assertEqual(reviewed["cadence"], "1h") + self.assertIn("review evidence", policy.read_text()) + + def test_invalid_dates_do_not_abort_reports(self): + finding = dict(id="RISK-F-9999", _path=pathlib.Path("test.md"), status="open", + next_check="not-a-date", severity="high", cadence="instant") + report = self.report([finding], [dict(id="RISK-POL-9999", next_check="bad")]) + self.assertIn("next_check is not a date", report) + self.assertIn("RISK-POL-9999 — invalid date", report) + self.assertIn("Publication handovers pending", report) + self.assertIsNone(lib.moment("2026-99-99")) + self.assertEqual(lib.moment("2026-09-05T00:00:00"), NOW) + + def test_regulatory_deferral_expires_and_bad_date_stays_visible(self): + policy = dict(id="RISK-POL-9999", next_check="2026-08-20", deferred_to="2026-09-06") + self.assertIn("Regulatory records due:\n none", self.report(regulatory=[policy])) + policy["deferred_to"] = "2026-09-04" + self.assertIn("RISK-POL-9999 — due", self.report(regulatory=[policy])) + policy["deferred_to"] = "bad" + self.assertIn("invalid date: deferred_to", self.report(regulatory=[policy])) + + def test_disclosure_obligations_survive_closure(self): + findings = [dict(id="RISK-F-9998", _path=pathlib.Path("a.md"), status="fixed", + disclosure="embargoed", embargo_condition="owner verifies", embargo_review="2026-09-01"), + dict(id="RISK-F-9999", _path=pathlib.Path("b.md"), status="fixed", + disclosure="public", publication="pending-handover")] + report = self.report(findings) + self.assertIn("RISK-F-9998 — embargo review due", report) + self.assertIn("RISK-F-9999 (fixed) — pending policy-nexus", report) + findings[0].pop("embargo_review") + self.assertIn("missing or invalid embargo_review", self.report(findings)) + findings[0]["embargo_review"] = "2027-01-01" + self.assertIn("Embargo reviews due:\n none", self.report(findings)) + + def test_bad_deferral_does_not_write_and_review_clears_old_deferral(self): + with tempfile.TemporaryDirectory() as directory: + path = pathlib.Path(directory) / "record.md" + original = '---\nid: RISK-F-9999\ncadence: instant\ndeferred_to: "2027-01-01"\n---\n' + path.write_text(original) + with patch.object(lib, "findings", side_effect=lambda: [lib.load(path)]), \ + patch.object(lib, "regulatory", return_value=[]), patch.object(lib, "now", return_value=NOW), \ + contextlib.redirect_stdout(io.StringIO()): + for value in ("bad", "2020-01-01"): + with self.assertRaises(SystemExit): + record_check.main(["RISK-F-9999", "defer", value, "operator reason"]) + self.assertEqual(path.read_text(), original) + record_check.main(["RISK-F-9999", "moved", "new evidence"]) + self.assertNotIn("deferred_to", lib.load(path)) + + def test_archived_completed_fix_is_not_stalled(self): + with tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + archive = root / "owner/workplans/archived" + archive.mkdir(parents=True) + (archive / "260901-TEST-WP-0001-fix.md").write_text( + '---\nid: TEST-WP-0001\nstatus: finished\nrepo: owner\n---\n') + finding = dict(id="RISK-F-9999", status="open", severity="high", fix_tracking="TEST-WP-0001") + with patch.object(fix_tracker, "ESTATE", root), patch.object(lib, "findings", return_value=[finding]), \ + patch.object(lib, "now", return_value=NOW), \ + patch.object(fix_tracker, "last_commit", return_value=NOW - dt.timedelta(days=100)): + report = "\n".join(fix_tracker.report()) + self.assertIn("finished [archived]", report) + self.assertNotIn("UNCHANGED", report) + + def test_accepted_obligation_requires_a_resolvable_basis_and_review(self): + finding = dict(id="RISK-F-9999", status="accepted", fix_tracking="unset", + accepted_by="operator", accepted_until="real data arrives", + determination="RISK-REG-9999", next_check="2026-08-01") + with patch.object(lib, "findings", return_value=[finding]), \ + patch.object(lib, "regulatory", return_value=[dict(id="RISK-REG-9999")]): + report = "\n".join(fix_tracker.report()) + self.assertIn("RECORDED ACCEPTANCE", report) + self.assertIn("not a fix or renewed acceptance", report) + self.assertNotIn("NO FIX TRACKING", report) + finding["next_check"] = "bad" + self.assertIn("ACCEPTANCE INCOMPLETE: next_check", "\n".join(fix_tracker.report())) + self.assertIn("NO FIX TRACKING", "\n".join(fix_tracker.report())) + finding["next_check"] = "2026-08-01" + with patch.object(lib, "findings", return_value=[finding]), patch.object(lib, "regulatory", return_value=[]): + self.assertIn("resolvable determination", "\n".join(fix_tracker.report())) + + def test_completed_source_does_not_hide_closure_evidence(self): + finding = dict(id="RISK-F-9999", _path=pathlib.Path("test.md"), status="open", + fix_tracking="TEST-WP-0001 (finished)", closure_condition="runtime acceptance receipt") + report = self.report([finding]) + self.assertIn("Closure evidence pending:\n RISK-F-9999 — runtime acceptance receipt", report) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/__pycache__/fix_tracker.cpython-312.pyc b/tools/__pycache__/fix_tracker.cpython-312.pyc deleted file mode 100644 index b115ce0..0000000 Binary files a/tools/__pycache__/fix_tracker.cpython-312.pyc and /dev/null differ diff --git a/tools/__pycache__/inbox_check.cpython-312.pyc b/tools/__pycache__/inbox_check.cpython-312.pyc deleted file mode 100644 index 5829c83..0000000 Binary files a/tools/__pycache__/inbox_check.cpython-312.pyc and /dev/null differ diff --git a/tools/__pycache__/register_lib.cpython-312.pyc b/tools/__pycache__/register_lib.cpython-312.pyc deleted file mode 100644 index 447cf7e..0000000 Binary files a/tools/__pycache__/register_lib.cpython-312.pyc and /dev/null differ diff --git a/tools/check_all.py b/tools/check_all.py new file mode 100644 index 0000000..77d35c2 --- /dev/null +++ b/tools/check_all.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""Run all read-only checks, retaining failures without hiding later reports.""" +import pathlib +import subprocess +import sys + +TOOLS = pathlib.Path(__file__).resolve().parent +STAGES = (("register_index.py", "--check"), ("register_check.py",), ("inbox_check.py",)) + + +def main() -> int: + failed = False + for stage in STAGES: + try: + result = subprocess.run([sys.executable, str(TOOLS / stage[0]), *stage[1:]]) + failed = result.returncode != 0 or failed + except OSError as exc: + print(f"{stage[0]}: could not run: {exc}", flush=True) + failed = True + print(flush=True) + return int(failed) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/fix_tracker.py b/tools/fix_tracker.py index 7baf80b..112277b 100644 --- a/tools/fix_tracker.py +++ b/tools/fix_tracker.py @@ -68,6 +68,7 @@ def resolve(fix_tracking: str) -> list[dict]: "id": full, "repo": fm.get("repo", path.parent.parent.name), "state": (state or "unreadable") + (" [archived]" if archived else ""), + "completed": str(state).lower() in ("done", "finished", "cancel"), "workplan_status": fm.get("status"), "moved": last_commit(path), }) @@ -81,6 +82,22 @@ def report() -> list[str]: continue tracking = f.get("fix_tracking") if not tracking or str(tracking).startswith("unset"): + if f.get("status") == "accepted": + missing = [key for key in ("accepted_by", "accepted_until", "determination") + if not f.get(key) or str(f[key]) == "unset"] + if lib.moment(f.get("next_check")) is None: + missing.append("next_check") + determinations = {r.get("id") for r in lib.regulatory()} + if f.get("determination") and f["determination"] not in determinations: + missing.append("resolvable determination") + if not missing: + lines.append( + f"{f['id']} — RECORDED ACCEPTANCE by {f['accepted_by']}; " + f"ends when: {f['accepted_until']}; basis {f['determination']}; " + f"review {f['next_check']} (not a fix or renewed acceptance)" + ) + continue + lines.append(f"{f['id']} — ACCEPTANCE INCOMPLETE: {', '.join(missing)}") lines.append(f"{f['id']} ({f.get('severity')}) — NO FIX TRACKING; owner {f.get('fix_owner')}") continue resolved = resolve(tracking) @@ -93,7 +110,7 @@ def report() -> list[str]: if r["moved"]: age = (now - r["moved"]).days window = STALL_DAYS.get(f.get("severity"), 30) - done = str(r["state"]).lower() in ("done", "finished", "cancel") + done = r.get("completed", False) flag = "" if done or age <= window else f" ← UNCHANGED {age}d (window {window}d)" lines.append(f"{f['id']} — {r['id']}: {r['state']}, last changed {r['moved']:%Y-%m-%d}{flag}") else: diff --git a/tools/record_check.py b/tools/record_check.py index b138b51..5f2e88b 100644 --- a/tools/record_check.py +++ b/tools/record_check.py @@ -57,6 +57,8 @@ def main(argv: list[str]) -> int: fail("`defer` needs a date and the operator's reason") until, why = rest[0], rest[1] new_rung, streak, nxt = rung, int(f.get("clean_streak", 0)), lib.moment(until) + if nxt is None or nxt <= now: + fail("`defer` needs a valid future date") line = f"- **{now:%Y-%m-%d}** — deferred to {until} by explicit operator decision: {why}" defer = until else: @@ -71,6 +73,8 @@ def main(argv: list[str]) -> int: } if defer: subs["deferred_to"] = defer + else: + text = re.sub(r"(?m)^deferred_to:.*\n", "", text) for key, value in subs.items(): quoted = f'"{value}"' if key not in ("cadence", "clean_streak") else value if re.search(rf"(?m)^{key}:", text): diff --git a/tools/register_check.py b/tools/register_check.py index 115b215..c30a758 100644 --- a/tools/register_check.py +++ b/tools/register_check.py @@ -16,7 +16,8 @@ NOW = lib.now() def main() -> int: - fs = [f for f in lib.findings() if lib.watched(f.get("status"))] + all_findings = lib.findings() + fs = [f for f in all_findings if lib.watched(f.get("status"))] lines: list[str] = [] def section(title: str, rows: list[str], quiet: str) -> None: @@ -50,9 +51,9 @@ def main() -> int: # reported by name, and never silently ignored. ids = {f["id"] for f in lib.findings()} malformed: list[str] = [] - for f in fs: + for f in all_findings: fid = f["id"] - for field in ("last_checked", "next_check", "embargo_since", "deferred_to"): + for field in ("last_checked", "next_check", "embargo_since", "embargo_review", "deferred_to", "escalation_sent"): if f.get(field) and lib.moment(f[field]) is None: malformed.append(f"{fid} — {field} is not a date: {f[field]!r}") if (c := f.get("cadence")) and c not in lib.CADENCE_NAMES: @@ -147,6 +148,10 @@ def main() -> int: # RISK-WP-0005-T01: the fix's own state, read from the owner's workplan # file rather than from our memory of what they told us. section("Fix state", fix_tracker.report(), "no finding claims a tracked fix") + section("Closure evidence pending", [ + f"{f['id']} — {f['closure_condition']}" + for f in fs if f.get("closure_condition") + ], "none recorded") if due_defaults: section("Defaults now due — apply them", due_defaults, "none") if deep: @@ -154,11 +159,26 @@ def main() -> int: embargo = [ f"{f['id']} — lifts when: {f.get('embargo_condition')}" - for f in fs + for f in all_findings if f.get("disclosure") == "embargoed" ] section("Embargoed", embargo, "none") + embargo_due = [] + for f in all_findings: + if f.get("disclosure") != "embargoed": + continue + when = lib.moment(f.get("embargo_review")) + if when is None: + embargo_due.append(f"{f['id']} — missing or invalid embargo_review; re-decide the hold") + elif NOW >= when: + embargo_due.append(f"{f['id']} — embargo review due {f['embargo_review']}; re-decide the hold") + section("Embargo reviews due", embargo_due, "none") + section("Publication handovers pending", [ + f"{f['id']} ({f.get('status')}) — pending policy-nexus handover: {f.get('publication_id', 'no publication id')}" + for f in all_findings if f.get("publication") == "pending-handover" + ], "none") + # T05 — an escalation nobody acknowledged is indistinguishable from one # never sent, which is the failure this register fixed for its own inbox # and not, until now, for the path that matters more. @@ -184,6 +204,14 @@ def main() -> int: reg = [] for r in lib.regulatory(): + invalid = [field for field in ("last_checked", "next_check", "review_by", "deferred_to") + if r.get(field) and lib.moment(r[field]) is None] + if invalid: + reg.append(f"{r.get('id')} — invalid date: {', '.join(invalid)}; review required") + continue + until = lib.moment(r.get("deferred_to")) + if until and NOW < until: + continue when = lib.moment(r.get("next_check")) or lib.moment(r.get("review_by")) if when is None: reg.append(f"{r.get('id')} — no next_check set") diff --git a/tools/register_lib.py b/tools/register_lib.py index 46337cc..b738d6c 100644 --- a/tools/register_lib.py +++ b/tools/register_lib.py @@ -82,7 +82,7 @@ def regulatory() -> list[dict]: d = REPO / "docs" / "regulatory" if not d.exists(): return [] - return sorted((load(p) for p in d.glob("*.md") if p.name != "README.md"), + return sorted((load(p) for p in d.rglob("*.md") if p.name != "README.md"), key=lambda r: r.get("id", "")) @@ -112,7 +112,12 @@ def moment(value) -> dt.datetime | None: if isinstance(value, dt.date): return dt.datetime.combine(value, dt.time(0, 0), tzinfo=dt.timezone.utc) if isinstance(value, str) and value not in ("", "unset"): - return dt.datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(dt.timezone.utc) + try: + parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return None + return (parsed.replace(tzinfo=dt.timezone.utc) if parsed.tzinfo is None + else parsed.astimezone(dt.timezone.utc)) return None diff --git a/workplans/RISK-WP-0006-keep-review-obligations-visible.md b/workplans/RISK-WP-0006-keep-review-obligations-visible.md new file mode 100644 index 0000000..630e78a --- /dev/null +++ b/workplans/RISK-WP-0006-keep-review-obligations-visible.md @@ -0,0 +1,130 @@ +--- +id: RISK-WP-0006 +type: workplan +title: "Keep review obligations visible when the index or records are stale" +domain: infotech +repo: risk-nexus +status: finished +owner: the-custodian +topic_slug: risk-nexus +created: "2026-09-05" +updated: "2026-09-05" +depends_on_workplans: + - RISK-WP-0005 +state_hub_workstream_id: "900ba790-c5b8-523c-947f-5cd743fa1d58" +--- + +# RISK-WP-0006 — keep review obligations visible + +## Problem and priority + +The [scope assessment](../history/2026-09-05-014333-scope-intent-assessment.md) +found that `make check` stops before showing overdue work, all four full legal +policies are outside review discovery, and disclosure obligations disappear +from the work list. These are the first priorities: they hide known obligations +and can be corrected within this repository without a new service or mandate. +Malformed dates must not defeat the repaired reporting path. + +## Tasks and acceptance + +### T01 — Run every check stage even if another fails + +```task +id: RISK-WP-0006-T01 +status: done +priority: high +state_hub_task_id: "44f58905-fc81-571f-b112-e37001b02ed9" +``` + +Run index, register and inbox checks independently, preserving a nonzero result +if any stage fails. A stale index and a broken register stage must still allow +the inbox stage to run. No regeneration or grading as a side effect. + +### T02 — Include full policies in the review workflow + +```task +id: RISK-WP-0006-T02 +status: done +priority: high +state_hub_task_id: "4a9cd603-7722-5e3b-833e-b65109f33d8a" +``` + +Discover nested regulatory records, excluding README catalogues. Full policies +must appear when due and accept recorded outcomes. `make due` must expose these +obligations to existing scheduled sessions, not filter them out. Invalid dates +must be reported without aborting the report; invalid deferrals must not write. + +### T03 — Keep disclosure obligations visible after closure + +```task +id: RISK-WP-0006-T03 +status: done +priority: high +state_hub_task_id: "3d250ebe-1d06-55f2-a4cc-8a716b12414f" +``` + +Report due or missing embargo review dates and pending publication handovers +across all findings, including fixed findings. Surface them through `make due`. +Do not publish, lift embargoes or change finding statuses automatically. + +### T04 — Correct the archived fix completion signal + +```task +id: RISK-WP-0006-T04 +status: done +priority: medium +state_hub_task_id: "79b0c627-9fff-59e2-a400-8d3e9585504b" +``` + +An archived completed workplan must not be reported as an unchanged unfinished +fix just because its display status carries an archive suffix. Preserve the +archive label and the documented whole-file timestamp limitation. + +### T05 — Verify, document and reconcile registration + +```task +id: RISK-WP-0006-T05 +status: done +priority: high +state_hub_task_id: "96d06e61-84e7-5c3a-9d79-b700408afaf7" +``` + +Add focused regression tests for the failures above using isolated files and +mocked external reads. Run them and the live read-only reporting path. Update +SCOPE, usage and the operational instructions, and reconcile the workplan and +task records with State Hub. Record actual registration results and residuals. + +## Scope boundary and next priorities + +This plan repairs visibility, not the overdue substantive reviews themselves. +External intake delivery, urgent incident handling, scheduler-to-session delivery +proof and the production declaration require separate operational decisions or +cross-repository work. They remain open in the assessment. Missing fix records +and the F-0011 notice require evidence reconciliation, not automatic closure. +No legal conclusions, grades, acknowledgements or messages are generated here. + +## Verification and completion evidence + +Implementation complete for T01–T04. Eight isolated regression tests pass via +`make test`, covering stage failures, nested policy discovery and recorded +reviews, invalid dates and deferrals, disclosure after closure, and archived +completed fixes. `git diff --check` passes. + +Live validation first ran with a stale index: every report still executed and +Make returned nonzero. After `make register`, `make check` and `make due` both +returned zero while exposing all four overdue policies, eight pending handovers, +three overdue findings and the newer F-0011 inbox notice. No finding or policy +was stamped reviewed, re-graded or closed. + +Registration: the normal CLI and an outside-sandbox retry timed out on State Hub +reads. A longer-timeout full reconciliation was stopped after prolonged API +reads without a completion result. Targeted registration succeeded +through the same workplan/task API and deterministic IDs used by the reconciler. +The workplan UUID is `900ba790-c5b8-523c-947f-5cd743fa1d58`; all five task +UUIDs are recorded beside their source blocks. Completion and the file binding +are synchronized through the API; the local index uses the reconciler renderer. + +Residuals: external contact delivery, incident clocks, independent execution +assurance, substantive overdue reviews, method review scheduling and production +transition decisions remain open. The updated activity definition is local +source; deployment/synchronization to activity-core was not performed. diff --git a/workplans/RISK-WP-0007-reconcile-owner-evidence.md b/workplans/RISK-WP-0007-reconcile-owner-evidence.md new file mode 100644 index 0000000..9180050 --- /dev/null +++ b/workplans/RISK-WP-0007-reconcile-owner-evidence.md @@ -0,0 +1,113 @@ +--- +id: RISK-WP-0007 +type: workplan +title: "Reconcile owner evidence and keep runtime closure obligations visible" +domain: infotech +repo: risk-nexus +status: finished +owner: the-custodian +topic_slug: custodian +created: "2026-09-05" +updated: "2026-09-05" +depends_on_workplans: + - RISK-WP-0006 +state_hub_workstream_id: "ac12733f-3f4b-5c7b-aee7-7115b9c4db72" +--- + +# RISK-WP-0007 — reconcile owner evidence + +The repaired reports expose work the register had not read. Reconcile it before +building another mechanism. Source remediation is progress; deployed acceptance +and credential invalidation require their own evidence. + +## T01 — Reconcile the qonto return + +```task +id: RISK-WP-0007-T01 +status: done +priority: high +state_hub_task_id: "d3f0a9b7-4374-5350-b0b4-d7ac4f782acb" +``` + +Read the newer inbox notice, source cadence/logger/endpoint and observer workplan. +Link QONTO-WP-0005 and KG-WP-0005-T03 to F-0011; replace the stale missing-source +claim with the concrete runtime acceptance obligation. Keep the medium grade +and open status until that obligation is demonstrated. Record a moved check. + +## T02 — Reconcile backup source remediation + +```task +id: RISK-WP-0007-T02 +status: done +priority: high +state_hub_task_id: "215b8b0b-aef0-5575-bf12-b08dc2941cbe" +``` + +Read RPF-WP-0029 and verify source fallback removal without displaying, testing +or copying credential material. Link provider invalidation/recovery tracking; +keep the low grade, open status and embargo until the original conditions hold. +Record a moved check, retaining the existing deadline. + +## T03 — Distinguish accepted obligations from missing fixes + +```task +id: RISK-WP-0007-T03 +status: done +priority: high +state_hub_task_id: "a17feb05-2c2c-5372-adbb-9d2c095f55fd" +``` + +Report an accepted record's accepter, expiry condition, determination and review +date when those fields resolve to a real record. Do not invent a fix workplan +for F-0008 or suppress incomplete acceptance records. Surface closure conditions +even when the source workplan is completed. Cover both paths with regression +tests. Inspect F-0008's technical evidence without renewing legal conclusions +or certifying that its real-data trigger remains false. + +## T04 — Record verification boundaries and register the result + +```task +id: RISK-WP-0007-T04 +status: done +priority: medium +state_hub_task_id: "ebf1edad-6e0e-5fdc-9781-5e79cff8b117" +``` + +Retain dated source evidence, explicit next evidence and owners. Inspect local +external-intake/publication and activity evidence; state exactly what remains +unverified. Update scope/state/index, run tests and reports, register this +workplan and its tasks, and verify completion in State Hub. + +## Boundaries + +No messages, acknowledgements, deployments, credential actions, publication, +new spending or automatic finding closure. Existing source-owner workplans +remain authoritative. This plan closes the register's reconciliation work, +not the owners' unresolved runtime obligations or the substantive legal reviews. + +## Completion evidence + +Completed 2026-09-05. RISK-V-0003 retains the inspected source evidence, +provenance and runtime/provider acceptance requirements. F-0011 now tracks +QONTO-WP-0005 and KG-WP-0005-T03; F-0010 tracks RPF-WP-0029-T02. Both received +`moved` outcomes and remain at `instant`; no same-sitting clean check was made. +Grades, open statuses, the credential embargo and original default dates remain. + +F-0008's technical inspection is explicitly not a completed substantive review. +Its existing acceptance is now reported with its determination and due date; +invalid/incomplete acceptances still produce missing-tracking warnings. The +closure-evidence section survives completed source workplans. + +Ten isolated tests pass via `make test`; document links and `git diff --check` +pass. The generated register and live checker expose the two waiting owner tasks, +recorded acceptance, four overdue full policies and eight publication handovers. + +State Hub registration succeeded for workplan +`ac12733f-3f4b-5c7b-aee7-7115b9c4db72` and all four tasks (UUIDs in source). +Completion status and backing-file binding are reconciled through the same API; +the local WORK-RECORDS index is regenerated with the State Hub renderer. + +Still external or unverified: runtime capture/observer acceptance, provider +invalidation/recovery receipts, acceptance-trigger facts and legal reviews, +external contact delivery and scheduled-session completion. Source searches +establish no stronger conclusion. No messages or acknowledgements were sent.