Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
16 changes: 12 additions & 4 deletions docs/adr/0076-typed-action-vocabulary-action-list-lens.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand Down
Loading