@junghanacs/entwurf 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +299 -0
  3. package/DELIVERY.md +4 -3
  4. package/README.md +99 -67
  5. package/VERIFY.md +13 -7
  6. package/docs/acp-backend-rail.md +1 -1
  7. package/docs/external-mcp-host.md +147 -9
  8. package/docs/setup-clean-host.md +221 -16
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +65 -20
  10. package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +17 -6
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +1 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +172 -5
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +152 -17
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +271 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +988 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-receive-omp.js +574 -0
  18. package/mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js +84 -0
  19. package/mcp/entwurf-bridge/src/index.ts +65 -19
  20. package/mcp/entwurf-bridge/tsconfig.build.json +19 -3
  21. package/mcp/tsconfig.json +10 -0
  22. package/package.json +14 -4
  23. package/pi/entwurf-capabilities.json +1 -0
  24. package/pi/meta-bridge-omp/entwurf-meta-omp/package.json +7 -0
  25. package/pi/omp-receive/entwurf-receive-omp/package.json +7 -0
  26. package/pi-extensions/entwurf-control.ts +12 -9
  27. package/pi-extensions/lib/entwurf-self-address.ts +18 -7
  28. package/pi-extensions/lib/meta-sender-identity.ts +1 -1
  29. package/pi-extensions/lib/meta-session.ts +219 -5
  30. package/pi-extensions/lib/mux-fresh-call.ts +171 -17
  31. package/pi-extensions/lib/omp-fresh-preflight.ts +275 -0
  32. package/pi-extensions/meta-bridge-omp.ts +1244 -0
  33. package/pi-extensions/meta-bridge-receive-omp.ts +666 -0
  34. package/run.sh +1041 -174
  35. package/scripts/agy-bridge.sh +4 -2
  36. package/scripts/check-copilot-birth-hook.ts +700 -23
  37. package/scripts/check-entwurf-self-address.ts +31 -0
  38. package/scripts/check-fresh-cut-gate.sh +12 -4
  39. package/scripts/check-gate-qualification.ts +8 -2
  40. package/scripts/check-harness-admission-parity.ts +143 -0
  41. package/scripts/check-meta-doctor-oracle.sh +20 -0
  42. package/scripts/check-omp-birth-hook.ts +1049 -0
  43. package/scripts/check-omp-fresh-preflight.ts +181 -0
  44. package/scripts/check-omp-receive-arm.ts +516 -0
  45. package/scripts/check-setup-qualification.sh +126 -0
  46. package/scripts/copilot-bridge-doctor.sh +104 -71
  47. package/scripts/copilot-bridge-install.sh +180 -20
  48. package/scripts/copilot-bridge-oracle.sh +223 -0
  49. package/scripts/copilot-bridge-uninstall.sh +137 -0
  50. package/scripts/dev-bin.sh +50 -9
  51. package/scripts/fake-copilot-vendor.sh +67 -0
  52. package/scripts/inventory-verification-surface.ts +1 -1
  53. package/scripts/meta-bridge-hook-log.sh +9 -1
  54. package/scripts/mutants/copilot-birth.json +96 -5
  55. package/scripts/mutants/mux-fresh-call.json +22 -22
  56. package/scripts/mutants/omp-birth.json +173 -0
  57. package/scripts/mutants/omp-fresh.json +300 -0
  58. package/scripts/mutants/omp-receive.json +135 -0
  59. package/scripts/mutants/pi-package-ownership.json +92 -0
  60. package/scripts/mutants/self-address.json +34 -0
  61. package/scripts/mutants/setup-verdict.json +148 -0
  62. package/scripts/mutants/source-install.json +32 -0
  63. package/scripts/omp-bridge-doctor.sh +315 -0
  64. package/scripts/omp-bridge-install.sh +221 -0
  65. package/scripts/omp-bridge-oracle.sh +154 -0
  66. package/scripts/omp-bridge-uninstall.sh +57 -0
  67. package/scripts/omp-mcp-bridge.sh +320 -0
  68. package/scripts/omp-mcp-config.py +392 -0
  69. package/scripts/omp-receive-doctor.sh +246 -0
  70. package/scripts/omp-receive-facts.ts +106 -0
  71. package/scripts/omp-receive-install.sh +228 -0
  72. package/scripts/omp-receive-uninstall.sh +60 -0
  73. package/scripts/omp-tool-surface.py +349 -0
  74. package/scripts/pi_settings_io.py +22 -0
  75. package/scripts/raw-omp-measure/README.md +420 -0
  76. package/scripts/raw-omp-measure/probe-extension.ts +76 -0
  77. package/scripts/raw-omp-measure/probe-receive-surface.ts +250 -0
  78. package/scripts/raw-omp-measure/source-audit.md +414 -0
  79. package/scripts/register-pi-package.py +525 -7
  80. package/scripts/register-pi-provider.py +162 -12
  81. package/scripts/smoke-agy-install-state.sh +86 -30
  82. package/scripts/smoke-agy-statusline-state.sh +9 -6
  83. package/scripts/smoke-omp-bridge-state.sh +221 -0
  84. package/scripts/smoke-omp-fresh-live.ts +497 -0
  85. package/scripts/smoke-omp-mcp-state.sh +327 -0
  86. package/scripts/smoke-omp-receive-live.ts +340 -0
  87. package/scripts/smoke-omp-receive-state.sh +196 -0
  88. package/scripts/smoke-setup-verdict.sh +291 -0
  89. package/scripts/smoke-user-scope-citizen.sh +388 -1
  90. package/scripts/tsconfig.json +2 -0
@@ -155,6 +155,26 @@ function row(facts: SelfAddressabilityFacts): { replyable: boolean; socketState:
155
155
  );
156
156
  }
157
157
 
158
+ // meta-session / NONE rail: no inbound transport at all. Mailbox facts and a live probe
159
+ // cannot buy replyability — there is nothing to land on. Distinct from the unsupplied
160
+ // (undefined) domain row below, which stays fail-closed for a caller that forgot the axis.
161
+ {
162
+ const noneFacts: SelfAddressabilityFacts = {
163
+ origin: "meta-session",
164
+ metaDeliveryDomain: "none",
165
+ recordBacked: true,
166
+ ownerAlive: true,
167
+ watchArmed: true,
168
+ probeAlive: true,
169
+ };
170
+ const noneRail = computeSelfAddressability(noneFacts);
171
+ ok("meta/none + all facts true → NOT replyable", noneRail.replyable === false);
172
+ ok(
173
+ "meta/none names the missing inbound rail (mailbox/probe facts cannot rescue it)",
174
+ noneRail.reason.includes("no inbound rail"),
175
+ );
176
+ }
177
+
158
178
  // meta-session with NO rail declared: we cannot say how a reply would travel, so we do not
159
179
  // claim it would arrive. The domain is derived from nativePushSupported(backend) — a caller
160
180
  // that forgets it gets a refusal, not an optimistic guess.
@@ -246,6 +266,17 @@ ok(
246
266
  /existsSync\s*\(/.test(piBody) && /controlSocketPathIn\s*\(\s*ENTWURF_DIR\s*,/.test(piBody),
247
267
  );
248
268
 
269
+ const metaBuilder = functionBody("buildTrustedMetaSenderEnvelope");
270
+ // FIRST of the two derivation claims so a binary-fallback mutant dies here, not at the seam pin.
271
+ ok(
272
+ "buildTrustedMetaSenderEnvelope does not fall back every non-native-push backend to self-fetch [QK:SELFADDR-NO-FALLBACK-SELF-FETCH]",
273
+ !/\?\s*"native-push"\s*:\s*"self-fetch"/.test(metaBuilder),
274
+ );
275
+ ok(
276
+ "buildTrustedMetaSenderEnvelope derives self-fetch through resolveMailboxWakeModeCapability(identity), not a backend-name list [QK:SELFADDR-MAILBOX-WAKE-SEAM]",
277
+ /resolveMailboxWakeModeCapability\s*\(\s*identity\s*\)/.test(metaBuilder),
278
+ );
279
+
249
280
  const selfRegion = toolRegion("entwurf_self");
250
281
  ok(
251
282
  "entwurf_self existsSync-probes the pi socket (alive vs expected, no synthesized path lie)",
@@ -394,11 +394,19 @@ esac
394
394
  # Static order: the gate precedes the first writer in each entrypoint, and the
395
395
  # preflight never runs the cut on the operator's behalf.
396
396
  gate_ln=$(grep -n 'preflight_v3_store setup' "$REPO/run.sh" | head -1 | cut -d: -f1) || true
397
- auth_ln=$(awk '/^setup_all\(\)/,/^}/{ if ($0 ~ /^ sync_auth$/) { print NR; exit } }' "$REPO/run.sh")
398
- if [ -n "$gate_ln" ] && [ -n "$auth_ln" ] && [ "$gate_ln" -lt "$auth_ln" ]; then
399
- ok "D4 setup_all calls the gate before sync_auth (line $gate_ln < $auth_ln)"
397
+ boot_ln=$(awk '/^setup_all\(\)/,/^}/{ if ($0 ~ /pnpm install --frozen-lockfile/) { print NR; exit } }' "$REPO/run.sh")
398
+ if [ -n "$gate_ln" ] && [ -n "$boot_ln" ] && [ "$gate_ln" -lt "$boot_ln" ]; then
399
+ ok "D4 setup_all calls the gate before the source bootstrap (line $gate_ln < $boot_ln)"
400
400
  else
401
- bad "D4 setup_all's gate is missing or sits after sync_auth (gate=$gate_ln sync_auth=$auth_ln)"
401
+ bad "D4 setup_all's gate is missing or sits after the source bootstrap (gate=$gate_ln bootstrap=$boot_ln)"
402
+ fi
403
+ # #86 A5 credential tripwire: the retired sync_auth mutation (OAuth alias copy +
404
+ # auth.json.bak) must never resurrect as an invocable surface in run.sh. Comments
405
+ # documenting the removal are allowed; a function definition or call is not.
406
+ if awk '!/^[[:space:]]*#/' "$REPO/run.sh" | grep -q 'sync_auth'; then
407
+ bad "D4b the retired sync_auth credential mutation reappeared as code in run.sh"
408
+ else
409
+ ok "D4b no sync_auth credential surface remains in run.sh (comments only)"
402
410
  fi
403
411
 
404
412
  ilp_gate=$(awk '/^install_local_package\(\)/,/^}/{ if ($0 ~ /preflight_v3_store install/) { print NR; exit } }' "$REPO/run.sh")
@@ -807,7 +807,7 @@ console.log(`\n[gate-qualification] self-test: ${passed} checks passed`);
807
807
  "bridge-boot-resume": 3,
808
808
  "bridge-command-boot": 9,
809
809
  "capability-cache": 3,
810
- "copilot-birth": 12,
810
+ "copilot-birth": 19,
811
811
  "copilot-launch": 14,
812
812
  "copilot-receive": 18,
813
813
  "fresh-cut": 1,
@@ -819,12 +819,18 @@ console.log(`\n[gate-qualification] self-test: ${passed} checks passed`);
819
819
  "mux-launcher-fence": 7,
820
820
  "mux-parent-artifact": 3,
821
821
  "pack-install": 1,
822
+ "pi-package-ownership": 6,
822
823
  "mux-resume-call": 12,
824
+ "omp-birth": 11,
825
+ "omp-fresh": 24,
826
+ "omp-receive": 11,
823
827
  "probe-ordering": 1,
824
828
  "release-gate": 12,
825
829
  "resume-args": 6,
826
830
  "resume-launch-identity": 6,
827
- "self-address": 3,
831
+ "self-address": 5,
832
+ "setup-verdict": 10,
833
+ "source-install": 2,
828
834
  "v2-surface": 7,
829
835
  "v2-visible-resume": 17,
830
836
  };
@@ -0,0 +1,143 @@
1
+ /**
2
+ * check-harness-admission-parity — the edge between two parity loops that were each closed and
3
+ * had nothing between them (#87 Bundle C).
4
+ *
5
+ * ── The defect this owns ──
6
+ *
7
+ * Two gates already held their own halves tight:
8
+ *
9
+ * `check-entwurf-capabilities` registry == META_CITIZEN_BACKENDS
10
+ * `fresh-call-surfaces.contract.test.ts` surfaces == FRESH_CALL_BACKENDS
11
+ *
12
+ * and NO file imported both constants. So a harness could be admitted as a full D6 citizen —
13
+ * birth, MCP hand, receive, a garden id on its own status line — while `entwurf_fresh_call`
14
+ * could not open it, and every gate in the repo stayed green. `docs/adding-a-harness.md` step 9
15
+ * says a backend is **supported** only when it can be opened as one visible fresh sibling, but
16
+ * that sentence had no consumer: the honest prose in `DELIVERY.md` ("Visible fresh is NOT
17
+ * implemented, so OMP is not a supported harness under step 9") sat there being true while a
18
+ * release package containing that backend passed the whole floor.
19
+ *
20
+ * `[측정]` 2026-08-30, on the Bundle A+B candidate: registry carried six backends, all D6;
21
+ * `FRESH_CALL_BACKENDS` carried three. The omp row read `unsupported` in the docs and shipped
22
+ * anyway. GLG's finding: **an `unsupported` label is not a partial-release permit.**
23
+ *
24
+ * ── The rule ──
25
+ *
26
+ * Every backend a meta-record may name is either
27
+ * (a) openable by `entwurf_fresh_call` — it walked step 9; or
28
+ * (b) a PRE-#82 admission, named here AND described as such in the delivery matrix.
29
+ *
30
+ * There is no third state. A post-#82 harness that is a citizen but not fresh-openable makes
31
+ * this gate red, which is what stops the release package rather than only the docs.
32
+ *
33
+ * ── Why the exception is a literal here plus a sentence there ──
34
+ *
35
+ * Same shape `check-release-gate-outcomes` cell 7 already uses for aggregate omissions, and for
36
+ * the same reason it states: *an exclusion that only this gate believes in is how the omission
37
+ * would come back.* The pair is deliberately redundant — the literal makes the exception a
38
+ * decision someone had to type, and the doc sentence makes it a thing an operator can read
39
+ * without opening this file. Neither half alone is the contract.
40
+ *
41
+ * This introduces NO new authority: the capability registry keeps its schema (there is no
42
+ * `supported` field and there must not be one — a self-declared grade is exactly what step 9
43
+ * refuses), and the fresh set stays the composition's own constant.
44
+ *
45
+ * Pure parse over shipped source + docs. No backend, no network, no subprocess.
46
+ */
47
+
48
+ import assert from "node:assert/strict";
49
+ import fs from "node:fs";
50
+ import path from "node:path";
51
+ import { fileURLToPath } from "node:url";
52
+ import { META_CITIZEN_BACKENDS, type MetaCitizenBackend } from "../pi-extensions/lib/meta-session.ts";
53
+ import { FRESH_CALL_BACKENDS } from "../pi-extensions/lib/mux-fresh-call.ts";
54
+
55
+ const REPO_DIR = fileURLToPath(new URL("..", import.meta.url));
56
+
57
+ let passed = 0;
58
+ function ok(label: string, cond: boolean): void {
59
+ assert.ok(cond, label);
60
+ console.log(` ok ${label}`);
61
+ passed++;
62
+ }
63
+
64
+ /**
65
+ * Backends admitted BEFORE the #82 step 9 contract, which `docs/adding-a-harness.md` step 9
66
+ * preserves explicitly: "a pre-contract backend that has not walked this step remains
67
+ * legacy/probe evidence and must not be described as supported until it is re-evaluated here."
68
+ * Copilot is the first admission made UNDER that contract (step 9's worked example), so nothing
69
+ * admitted after it qualifies for this list.
70
+ *
71
+ * `claude-code` and `pi` are also pre-contract and are deliberately absent: they are fresh-
72
+ * openable, so they need no exception. An exception is for a backend that CANNOT be opened —
73
+ * never a convenience for one that simply has not been wired yet.
74
+ *
75
+ * Each value is the file and the sentence in it that carries the exception for a reader. The
76
+ * sentence must be the one that says WHY the backend is not step-9 supported; a row that merely
77
+ * mentions the backend is not an exception.
78
+ */
79
+ const PRE_82_LEGACY: Partial<Record<MetaCitizenBackend, [file: string, sentence: string]>> = {
80
+ antigravity: [
81
+ "DELIVERY.md",
82
+ "Admitted before the #82 step 9 contract and not re-evaluated under it, so it is legacy citizen evidence, not a step-9 supported harness",
83
+ ],
84
+ codex: ["DELIVERY.md", "No owned native-citizen install/invocation lane."],
85
+ };
86
+
87
+ const fresh = new Set<string>(FRESH_CALL_BACKENDS);
88
+ const legacy = Object.keys(PRE_82_LEGACY);
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // 1. The join itself. This assertion is the entire point of the file: it is the
92
+ // first line in the repo that reads both constants at once.
93
+ // ---------------------------------------------------------------------------
94
+ const unaccounted = META_CITIZEN_BACKENDS.filter((b) => !fresh.has(b) && !(b in PRE_82_LEGACY));
95
+ ok(
96
+ "[QK:ADMISSION-FRESH-PARITY] every citizen backend is either openable by entwurf_fresh_call or a declared pre-#82 legacy admission — a post-contract harness that mints records but cannot be opened is not admissible, and an `unsupported` note is not a partial-release permit (docs/adding-a-harness.md step 9). Unaccounted: " +
97
+ (unaccounted.length === 0 ? "none" : unaccounted.join(", ")),
98
+ unaccounted.length === 0,
99
+ );
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // 2. The exception must be readable OUTSIDE this file. A literal nobody else
103
+ // carries is how the omission comes back (check-release-gate-outcomes cell 7).
104
+ // ---------------------------------------------------------------------------
105
+ for (const backend of legacy) {
106
+ const [file, sentence] = PRE_82_LEGACY[backend as MetaCitizenBackend] as [string, string];
107
+ const body = fs.readFileSync(path.join(REPO_DIR, file), "utf8");
108
+ ok(
109
+ `[QK:ADMISSION-LEGACY-DOCUMENTED] ${backend}'s pre-#82 exception is carried by a sentence an operator can read in ${file}, not only by this gate's literal`,
110
+ body.includes(sentence),
111
+ );
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // 3. The two states are exclusive, and the exception list has no dead entries.
116
+ // A backend that got wired for fresh while keeping its legacy excuse would
117
+ // leave a stale "cannot be opened" sentence in the matrix pointing at a
118
+ // capability that now exists.
119
+ // ---------------------------------------------------------------------------
120
+ for (const backend of legacy) {
121
+ ok(
122
+ `[QK:ADMISSION-LEGACY-EXCLUSIVE] ${backend} is excused OR fresh-openable, never both — a backend that walked step 9 must lose its exception and its matrix sentence together`,
123
+ !fresh.has(backend),
124
+ );
125
+ ok(
126
+ `[QK:ADMISSION-LEGACY-SCOPE] ${backend} is still a citizen backend — an exception for a backend no record may name is dead weight that hides the next real one`,
127
+ (META_CITIZEN_BACKENDS as readonly string[]).includes(backend),
128
+ );
129
+ }
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // 4. The reverse direction. The fresh set naming something that can never mint a
133
+ // record would be a launchable window with no address to call home from —
134
+ // `freshCall` would open it and the callback could carry no garden id.
135
+ // ---------------------------------------------------------------------------
136
+ const openableNonCitizens = [...fresh].filter((b) => !(META_CITIZEN_BACKENDS as readonly string[]).includes(b));
137
+ ok(
138
+ "[QK:ADMISSION-FRESH-IS-CITIZEN] every fresh-openable backend is also a citizen backend — opening a harness that cannot mint a record would produce a window with no address to call back from. Offenders: " +
139
+ (openableNonCitizens.length === 0 ? "none" : openableNonCitizens.join(", ")),
140
+ openableNonCitizens.length === 0,
141
+ );
142
+
143
+ console.log(`[check-harness-admission-parity] ${passed} assertions ok`);
@@ -526,6 +526,26 @@ else
526
526
  fi
527
527
  cp "$TMP/hook-log-precopilot.bak" "$AGENT/meta-bridge-hook.log"
528
528
 
529
+ # M9c — an OMP line in the shared hook log must not move this doctor either (#87). The
530
+ # omp birth unit is an in-process EXTENSION, and its scope fence writes a line for every
531
+ # task subagent it refuses, so this file now carries a second rail's routine traffic. Its
532
+ # ERROR lines are mint/marker faults on that rail and its recovery token is
533
+ # `create`/`attach`, never `armed watch` — so counting them here would redden the CLAUDE
534
+ # doctor for a fault it does not own and cannot prescribe a fix for. Same expect-GREEN
535
+ # shape as M9b: the defect this guards makes a HEALTHY host go red.
536
+ cp "$AGENT/meta-bridge-hook.log" "$TMP/hook-log-preomp.bak"
537
+ {
538
+ echo "2026-08-27T00:00:02.000Z ERROR [omp] upsert failed (edge=session_start, native=01a042da-537a-7770-a275-7b8162eecca4): store refused"
539
+ echo "2026-08-27T00:00:03.000Z INFO [omp] scope-refused edge=session_start mode=print: not the visible tui host, no record minted"
540
+ } >> "$AGENT/meta-bridge-hook.log"
541
+ run_doctor
542
+ if [ "$DOC_RC" -eq 0 ]; then
543
+ ok "an omp ERROR in the shared hook log leaves the CLAUDE doctor green (rails stay separate)"
544
+ else
545
+ bad "[QK:HOOK-LOG-OMP-RAIL-SCOPED] an omp ERROR in the shared hook log turned the CLAUDE doctor red — the hook-log judgement does not scope out the omp rail:"$'\n'"$(printf '%s\n' "$DOC_OUT" | grep -E '^ (FAIL|WARN)' | sed 's/^/ /')"
546
+ fi
547
+ cp "$TMP/hook-log-preomp.bak" "$AGENT/meta-bridge-hook.log"
548
+
529
549
  # M10 — deployed writer bundle missing: staleness becomes unknowable.
530
550
  mv "$PLANTED/lib/meta-session.ts" "$TMP/meta-session.bak"
531
551
  expect_red "installed writer bundle missing" "deployed writer version is UNKNOWN"