diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 7667ad7d..82ca2622 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -7062,7 +7062,7 @@ The webview cannot import from `src/` (it is loaded as a plain script into a `de ## 248. Steps view: reclassify comment-only rows as a non-opaque note row -> πŸ”’ **Filed 2026-07-30 β€” not started.** ADR-first: gated on **ADR 0076 Amendment A**, which is PROPOSED and not yet ratified. Do not build ahead of the ruling. +> πŸ”’ **Filed 2026-07-30 β€” not started. UNBLOCKED 2026-07-30:** the ADR gate is cleared β€” **ADR 0076 Amendment A is ACCEPTED and in force** (owner-ratified 2026-07-30), so the grammar widening this item needs is authorized and the build may proceed. Treat Amendment A Β§A.4's invariants as **build gates, not caveats**, and note Β§A.6: this item does **not** fix comment re-attachment on move/delete, nor the parent-nesting of a comment at the end of an `if`/`for` body. **Cluster:** IDE & Authoring. **Priority:** P2. **Verdict:** build (ADR-first). **Severity:** medium β€” three of the sub-defects are shipped user-visible breakage, not a coverage gap. diff --git a/docs/adr/0076-typed-action-vocabulary-action-list-lens.md b/docs/adr/0076-typed-action-vocabulary-action-list-lens.md index dbab147c..18986efe 100644 --- a/docs/adr/0076-typed-action-vocabulary-action-list-lens.md +++ b/docs/adr/0076-typed-action-vocabulary-action-list-lens.md @@ -1,6 +1,6 @@ # ADR 0076 β€” Typed action vocabulary + structured action-list lens over Python Handlers -**Status:** Accepted (2026-07-10) β€” ratified by the owner 2026-07-10; the PLAN-8 lanes may build. Gating rule: **phase 1 (the vocabulary) requires only the #26-amendment merge; phases 2–3 require this ADR Accepted.** In practice phase 1 builds after Acceptance anyway β€” its v1 roster is fixed by Β§2 and MULTISESSION-PLAN-8 bundles it with phase 2a in one lane. **Two amendments are PROPOSED and awaiting owner ratification (2026-07-30):** Amendment A β€” a `note` row kind so comment-only rows stop projecting as opaque `code`, superseding ADR 0106 Β§5 (L) (BACKLOG #248); Amendment B β€” ADR 0089 Phase D "helper descent", **not buildable** until its three preconditions clear. Neither is in force; Β§3/Β§4/Β§5 read as originally accepted until the owner rules. +**Status:** Accepted (2026-07-10) β€” ratified by the owner 2026-07-10; the PLAN-8 lanes may build. Gating rule: **phase 1 (the vocabulary) requires only the #26-amendment merge; phases 2–3 require this ADR Accepted.** In practice phase 1 builds after Acceptance anyway β€” its v1 roster is fixed by Β§2 and MULTISESSION-PLAN-8 bundles it with phase 2a in one lane. **Amendment A β€” ACCEPTED, ratified by the owner 2026-07-30 and IN FORCE:** a `note` row kind so comment-only rows stop projecting as opaque `code`, superseding ADR 0106 Β§5 (L); Β§3's enum and Β§4's ladder read as amended, and BACKLOG #248 is the build. **Amendment B β€” still PROPOSED and NOT ratified:** ADR 0089 Phase D "helper descent" is specified and priced but **not buildable**; its yield is unmeasured and may be negative, and its three Β§B.4 preconditions are unmet. Do not read A's ratification as covering B. **Deciders:** owner + IDE/DX working group **Related:** BACKLOG **#222** (this build), **#26 amendment** (the narrow carve-out this ADR operates under), **#221** (sibling IDE-polish lane), the deep-research findings ([`docs/research/ide-low-code-options.md`](../research/ide-low-code-options.md) β€” verified precedents: InterSystems low-code custom editors, Kaoto/Karavan/AWS Workflow Studio, Iguana annotations, Corepoint action-lists), ADR 0007/0033/0014 (the sanctioned config-as-data GUIs), ADR 0072 (traced dry-run β€” the live values rendered beside action rows), ADR 0010/0043 (`db_lookup`/`fhir_lookup` β€” the sanctioned read-only lookups the lens renders as DBSelect-style rows), ADR 0035 (IDE workspace-trust β€” `lens` CLI calls are exec-gated like every CLI call), CLAUDE.md Β§9 (PHI), Β§12 (the amended bright line). Plan: [`docs/releases/MULTISESSION-PLAN-8.md`](../releases/MULTISESSION-PLAN-8.md) (L2 builds phases 1+2a; L3 builds phase 2b; L4 = phase 3, owner-gated). @@ -133,9 +133,17 @@ Phase 2b shipped the lens with the live-value slot **stubbed** (each row rendere ## Amendment A (2026-07-30) β€” a `note` row kind: comment-only rows stop projecting as opaque `code` (BACKLOG #248) -> **Status of this amendment: PROPOSED (2026-07-30) β€” owner ratification required.** It supersedes an -> owner-ratified decision (ADR 0106 Β§5 (L), ratified 2026-07-13), so it cannot be adopted by the session -> that drafted it. The evidence in Β§A.3 is verified against `main`; the decision is not taken. +> **Status of this amendment: ACCEPTED β€” ratified by the owner 2026-07-30.** It supersedes an +> owner-ratified decision (ADR 0106 Β§5 (L), ratified 2026-07-13); that supersession is now in force, and +> ADR 0106 Β§5 (L)'s "honest degrade" projection of an inserted Comment as a `code` row no longer holds. +> The evidence in Β§A.3 was verified against `main` before ratification. +> +> **In force means the grammar changed, not that the build is done.** Β§3's row enum now includes `note` +> and `diagnostic`; Β§4's ladder gains `note` as a sibling of the typed rows. BACKLOG #248 is the build +> and is now unblocked. The invariants in Β§A.4 are **build gates, not caveats** β€” in particular the +> pragma allowlist, the `_merge_code_rows` docstring exclusion, and the rule that a note edit takes its +> indentation and `#` prefix form from the existing line rather than the insert normalizer. Β§A.6's two +> known-wrong behaviours are **not** fixed by this amendment and must not be reported as fixed. Β§3's row enum ends at `{ "kind": "code", … } // verbatim, unrecognized`, and Β§4's ladder sends "everything else" there. A standalone comment is therefore projected as an opaque `Code` step. This diff --git a/docs/adr/README.md b/docs/adr/README.md index 2405b5bf..c5245a22 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -108,7 +108,7 @@ what is withheld and what you can request. | [0073](0073-ownership-scoped-recovery-single-consumer-lanes.md) | Ownership-scoped recovery + single delivery consumer per outbound lane β€” the N-active-shards-on-one-unified-store reliability runtime (builds ADR 0063's deferred primitive): `reset_stale_inflight(owned=OwnedLanes)` scopes startup/DR crash recovery to a shard's config-graph lanes (channel_id for ingress/routed/response, destination_name for outbound; empty set matches nothing; residual-`IN`/`ANY` predicate keeps the WS-B ready-index seek); deterministic rendezvous (sha256 HRW) outbound-lane ownership over the pinned shard universe, gated at the wake boundary + pooled lane provider + per-lane spawn (predicate, not set β€” a reload-dropped lane keeps exactly its owner); `--shard`+`[cluster]` refused fail-closed; shard-set-changing reloads refused (fleet restart required); owner-only outbound controls/purge (409 names the owner; `/connections` rows carry `owner_shard`); sharded-only non-owned-lane buildup/stall watchdog (hung-owner paging). N-active stays gated on the clean 4-engine no-loss bench before SYSTEM-REQUIREMENTS calls it supported | Accepted (2026-07-06) β€” built | | [0074](0074-adopter-capacity-estimator.md) | Adopter-run capacity estimator (BACKLOG #96) β€” productize the **built** `harness/load/` rate-walk + zero-loss reconcile as a supported `messagefoundry capacity` command an adopter points at *their* box/store/config to answer "does this carry my ~36 msg/s hospital with headroom?"; reports the **per-interface** no-loss ceiling + engine-wide aggregate + a **backend-aware limiting-factor** label + provision-at-≀50%-of-ceiling guidance. Hard requirements: **isolated throwaway store** (refuse to run against a non-isolated/production store β€” count-and-log intact), **synthetic PHI-free** payloads only (ADR 0030), backend-aware labels (SQLite knob rankings do **not** transfer to server backends β€” the B12 lesson), explicit harness-ceiling caveats (~450/s ACK per driver, ~135–144/s delivered per sink, poller-zero β‡’ sub-ceiling knee). v1 = rate-walk + limiting-factor labels; deeper per-stage diagnostics deferred. A productization of throughput-campaign evidence (PR #768), not a new measurement effort | Accepted (2026-07-07) β€” ratified. β›” **BUILD GATED (2026-07-14)**: a validity re-check vs STEP-4 Arm 0 found **14 confirmed blockers** in the *measurement* method (the named "only success gate" over-reports by **3–5.5Γ—**; the poller-zero failure mode *satisfies* it; the estimand is intake not delivery; the aggregate-is-the-sum rule is measured-false; the ceiling is instant-partner). **Premise + hard requirements + the fail-closed guard layer still hold and remain buildable.** See the ADR's 2026-07-14 Amendment | | [0075](0075-per-hop-sql-statement-batching.md) | Per-hop SQL statement batching (`[pipeline].batch_handoff_statements`, default-ON (emergency off-switch), fail-closed, SQL-Server-only) β€” the last [ADR 0069](0069-durable-write-throughput-lever.md)-named feed lever ("batching SQL statements per executor hop"): fold a multi-statement handoff **body** (guard-DELETE + inserts + finalize applock + `messages.status` UPDATE + event, from the SAME shared `(sql,params)` builders that keep the async/sync twins in lockstep) into 1–2 `pyodbc.execute()` batches, cutting **network round-trips + aioodbc executor crossings β€” NOT transactions** (`commits/msg` stays 2.000; the ADR 0069 cross-lane/commit fence is not hit). Attacks the serial-RT co-bottleneck the [ADR 0071](0071-cut-executor-round-trips-b5.md) B5 fusion NO-GO could not (the ~11 ms inter-box store RTT Γ— ~4–5 RT/msg) and works on the **default async path**. Microbench (adversarially re-reviewed, counts VERIFIED honest): per-hop drop 27–50%, but the **β‰₯40% figure is CONDITIONAL on the applock-rc-fold** β€” under the strict interpretation 27–33% clears nothing, so the microbench JUSTIFIES a live-rig e2e A/B, it does not substitute for it. Content-vs-infra error attribution + a golden-SQL/living RT-count CI gate are load-bearing. **Promoted default-ON 2026-07-08** (Bench B distance-insurance A/B: harmless-near + helps-far, green SS correctness precondition) β€” flag retained only as an emergency off-switch | Accepted (2026-07-07) β€” promoted default-ON 2026-07-08 | -| [0076](0076-typed-action-vocabulary-action-list-lens.md) | Typed action vocabulary + structured action-list lens over Python Handlers (BACKLOG #222, under the #26 amendment) β€” phase 1: `messagefoundry/actions.py`, pure typed helpers mirroring Corepoint's action classes over the existing `Message` API (control flow stays native Python; no flow wrappers); phase 2: static-only `lens parse --json` (stdlib `ast`, never imports/executes config) + a VS Code `CustomTextEditorProvider` rendering any *parseable* Handler as a Corepoint-style action-list view β€” typed rows for the bounded structural grammar, in-place read-only `code` rows for anything else (coverage invariant: rows exactly partition the def body β€” never drop/reorder/synthesize) and whole-file refusal only on parse failure; phase 3 (bake + owner-go gated): row-scoped line-splice rewrites (byte-stable outside the edited row). The `.py` stays the **only artifact and execution path** β€” no interpreter, no declarative artifact, no canvas; InterSystems guardrails adopted (sync-on-save, one-editor-at-a-time, degrade-to-text-editor); live values reuse the ADR 0072 stream + `--show-phi` gate unchanged; stdlib-only, no new runtime dep (libcst deferred) | Accepted (2026-07-10) β€” two amendments PROPOSED 2026-07-30, neither in force: A) a `note` row kind so comment-only rows stop projecting as opaque `code`, superseding ADR 0106 Β§5 (L), and reconciling the Β§3 enum to the kinds the parser already emits (BACKLOG #248); B) ADR 0089 Phase D "helper descent", specified and priced but **not buildable** β€” yield unmeasured and possibly negative | +| [0076](0076-typed-action-vocabulary-action-list-lens.md) | Typed action vocabulary + structured action-list lens over Python Handlers (BACKLOG #222, under the #26 amendment) β€” phase 1: `messagefoundry/actions.py`, pure typed helpers mirroring Corepoint's action classes over the existing `Message` API (control flow stays native Python; no flow wrappers); phase 2: static-only `lens parse --json` (stdlib `ast`, never imports/executes config) + a VS Code `CustomTextEditorProvider` rendering any *parseable* Handler as a Corepoint-style action-list view β€” typed rows for the bounded structural grammar, in-place read-only `code` rows for anything else (coverage invariant: rows exactly partition the def body β€” never drop/reorder/synthesize) and whole-file refusal only on parse failure; phase 3 (bake + owner-go gated): row-scoped line-splice rewrites (byte-stable outside the edited row). The `.py` stays the **only artifact and execution path** β€” no interpreter, no declarative artifact, no canvas; InterSystems guardrails adopted (sync-on-save, one-editor-at-a-time, degrade-to-text-editor); live values reuse the ADR 0072 stream + `--show-phi` gate unchanged; stdlib-only, no new runtime dep (libcst deferred) | Accepted (2026-07-10) β€” **Amendment A ACCEPTED 2026-07-30 (owner-ratified, in force):** a `note` row kind so comment-only rows stop projecting as opaque `code`, **superseding ADR 0106 Β§5 (L)**, and reconciling the Β§3 enum to the kinds the parser already emits; build = BACKLOG #248. **Amendment B still PROPOSED, not ratified:** ADR 0089 Phase D "helper descent", specified and priced but **not buildable** β€” yield unmeasured and possibly negative, Β§B.4 preconditions unmet | | [0077](0077-action-bound-step-up.md) | Action-bound step-up re-verification for durable-takeover operations (ASVS 2.2.4 / BACKLOG #187) β€” a fresh re-authentication bound to the *specific* privileged action, not merely to a recent login, so a hijacked live session cannot silently perform a durable takeover | Accepted | | [0078](0078-certificate-revocation-posture.md) | Certificate revocation posture (OCSP/CRL, ASVS 12.1.4, BACKLOG #201) β€” **enforced start-time refusal + delegated proxy**, NOT in-engine OCSP (stdlib `ssl` has no OCSP/CRL fetch; a hand-rolled responder fetch fights on-prem offline-by-default). Refines [ADR 0002](0002-phase2-transport-security-and-strong-auth.md)'s *documented* revocation residual into an **enforced** control: `serve` REFUSES to start an in-process, off-loopback `[api]` TLS bind (`tls_cert_file` set + non-loopback `host`) UNLESS revocation is *proven in front* β€” a declared TLS-terminating proxy (`tls_terminated_upstream` + `trusted_proxies`, which does its own OCSP-must-staple/CRL) OR the operator opt-out `MEFOR_TLS_REVOCATION_ATTESTED=1`. Secure default = refuse; the loopback default + proxy-terminated paths start **byte-identically** (the pure `config/tls_policy.py:in_process_tls_revocation_refused` predicate short-circuits). Compensating controls: the SQL-Server SChannel path already does OS-managed revocation, and `pipeline/cert_expiry.py` alerts on expiring certs (steering short-lived certs). **Amendment 2026-07-12 (BACKLOG #201 residual):** extends the SAME posture-keyed refusal to the OUTBOUND verifying-TLS connectors β€” pure `revocation_hop_disposition(*, is_phi, production, is_loopback_hop, proxy_proven, attested)` + `RevocationHopGuard` in `config/tls_policy.py`, wired into MLLP-over-TLS egress, the REST/SOAP/FHIR https paths (`refuse_unrevoked_verified_hop`), and the Postgres asyncpg store hop (`_refuse_store_revocation`); per-connection `tls_revocation_attested` + the blanket `MEFOR_TLS_REVOCATION_ATTESTED` env are the opt-outs. Composes with #200 (fires only on a VERIFYING hop β€” no double-refusal). Still out of scope: SQL-Server/SChannel (already OS-managed), DICOM-SCU/FTPS, the FhirLookup read path. Flips the ASVS 12.1.4 row from documented-residual to enforced-delegation | Accepted (2026-07-10; amended 2026-07-12) β€” built | | [0079](0079-kerberos-idp-session-coordination.md) | Kerberos/AD engine-session lifetime coordinated with the directory (IdP) β€” terminate engine sessions when the directory revokes or disables the account, rather than letting a local session outlive its AD principal. **Amendment 2026-07-21:** mechanism 1's preferred input β€” the Kerberos ticket `endtime` β€” is **unobtainable** via pyspnego 0.12.1 (no expiry on the public `ContextProxy`; `SSPIProxy.step()` discards sspilib's `AcceptContextResult.expiry`), so on the Kerberos/LDAPS path it would degrade to a second local constant dressed as directory data. ASVS 7.1.3 therefore **closed by ACCEPTANCE** (signed register row, theme 3) and this ADR's Proposedβ†’Accepted trigger is **NOT fired**; mechanism 1 ships only where the datum genuinely exists β€” the federated `id_token.exp` session cap. Mechanism 2 (background re-validation loop) stays deferred. **Amendment 2026-07-22: mechanism 2 is BUILT.** The deferral's stated cost was void β€” the candidate set derives from the existing `list_users()` + `list_sessions()`, so **no `sessions` schema change on any backend** (provenance columns were only ever mechanism 1's need). Recorded narrowing: `require_step_up` performs **no** directory bind (it compares the stored `reauth_at`), so the step-up surface is protected by inability to REFRESH β€” leaving a ≀`step_up_max_age_seconds` residual β€” while **bulk/raw PHI reads** (`require_phi_read`, 120/min) and **connection start/stop** (`require_paced`) survived to the 12 h cap. Adds three properties the design did not name: **two-strike** before revoking (the lookup returns one `None` for disabled βˆͺ deleted βˆͺ wrong-search-base), **all-or-nothing passes** (planned in `auth/reconcile.py` before any write), and a **mass-revoke circuit breaker** β€” a bad search base answers "not found" for everyone, so a pass exceeding **both** `ad_session_revoke_max` (5) **and** `ad_session_revoke_max_fraction` (0.34) aborts + alerts. AND, not OR: the floor alone signs out a 5-person site, the proportion alone fires on a 3-of-3 offboarding. Group re-diff rides the pass free (demotions no longer wait for a login); channel scope deliberately excluded. Default OFF (`ad_session_recheck_seconds = 0`) | Accepted (mechanism 2 built 2026-07-22; mech 1 Kerberos path closed by acceptance, federated path shipped in ADR 0142) |