@junghanacs/entwurf 0.12.8 → 0.12.10
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.
- package/AGENTS.md +134 -248
- package/BASELINE.md +1 -1
- package/CHANGELOG.md +39 -1
- package/DELIVERY.md +5 -4
- package/README.md +148 -23
- package/VERIFY.md +3 -2
- package/demo/README.md +2 -2
- package/demo/demo.sh +7 -1
- package/docs/setup-clean-host.md +24 -7
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +168 -81
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +12 -12
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +5 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-core.js +26 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +10 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +16 -18
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +13 -13
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-resume-args.js +31 -41
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +8 -9
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +32 -25
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +4 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-lock.js +5 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +14 -13
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-spawn-production.js +11 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +15 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +500 -54
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +8 -3
- package/mcp/entwurf-bridge/dist/scripts/agy-imprint.js +14 -2
- package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +155 -29
- package/mcp/entwurf-bridge/src/index.ts +200 -94
- package/package.json +11 -9
- package/pi-extensions/entwurf-control.ts +81 -39
- package/pi-extensions/lib/acp/models.ts +12 -12
- package/pi-extensions/lib/acp/overlay.ts +5 -3
- package/pi-extensions/lib/entwurf-core.ts +26 -9
- package/pi-extensions/lib/entwurf-deliverability.ts +10 -6
- package/pi-extensions/lib/entwurf-fact-provider.ts +24 -19
- package/pi-extensions/lib/entwurf-facts.ts +13 -13
- package/pi-extensions/lib/entwurf-resume-args.ts +40 -46
- package/pi-extensions/lib/entwurf-self-address.ts +8 -9
- package/pi-extensions/lib/entwurf-v2-contract-schema.ts +7 -2
- package/pi-extensions/lib/entwurf-v2-contract.ts +32 -25
- package/pi-extensions/lib/entwurf-v2-decider.ts +8 -8
- package/pi-extensions/lib/entwurf-v2-lock.ts +5 -4
- package/pi-extensions/lib/entwurf-v2-production.ts +22 -13
- package/pi-extensions/lib/entwurf-v2-spawn-production.ts +11 -3
- package/pi-extensions/lib/meta-sender-identity.ts +15 -5
- package/pi-extensions/lib/meta-session.ts +543 -58
- package/pi-extensions/meta-bridge-hook.ts +8 -2
- package/run.sh +78 -67
- package/scripts/agy-bridge-config.py +249 -49
- package/scripts/agy-bridge.sh +59 -14
- package/scripts/agy-imprint.ts +15 -1
- package/scripts/check-acp-carrier-augment.ts +34 -2
- package/scripts/check-acp-sdk-surface.ts +22 -11
- package/scripts/check-acp-session-reuse.ts +1 -1
- package/scripts/check-acp-session-store.ts +3 -3
- package/scripts/check-agy-permission-matrix.py +655 -0
- package/scripts/check-agy-sender-identity.ts +83 -0
- package/scripts/check-entwurf-control-rpc.ts +2 -2
- package/scripts/check-entwurf-deliverability.ts +9 -6
- package/scripts/check-entwurf-fact-provider.ts +16 -8
- package/scripts/check-entwurf-facts.ts +13 -13
- package/scripts/check-entwurf-resume-args.ts +25 -63
- package/scripts/check-entwurf-self-address.ts +187 -4
- package/scripts/check-entwurf-session-identity.ts +7 -6
- package/scripts/check-entwurf-v2-contract.ts +2 -2
- package/scripts/check-entwurf-v2-production.ts +9 -7
- package/scripts/check-entwurf-v2-spawn-production.ts +8 -4
- package/scripts/check-entwurf-v2-surface.ts +327 -14
- package/scripts/check-fresh-cut-gate.sh +305 -4
- package/scripts/check-gate-qualification.ts +785 -0
- package/scripts/check-meta-identity-consumers.ts +501 -1
- package/scripts/check-meta-listing.ts +91 -9
- package/scripts/check-meta-receiver-marker.ts +54 -0
- package/scripts/check-model-lock.ts +1 -1
- package/scripts/check-shell-quote.ts +2 -1
- package/scripts/lib/mutation-qualify.ts +794 -0
- package/scripts/meta-bridge-fresh-cut.ts +164 -28
- package/scripts/mutants/acp-augment.json +30 -0
- package/scripts/mutants/agy-permission.json +144 -0
- package/scripts/mutants/meta-identity.json +17 -0
- package/scripts/mutants/self-address.json +59 -0
- package/scripts/mutants/v2-surface.json +87 -0
- package/scripts/pi_settings_io.py +65 -0
- package/scripts/register-pi-package.py +183 -37
- package/scripts/register-pi-provider.py +68 -10
- package/scripts/smoke-acp-raw-turn-live.ts +1 -1
- package/scripts/smoke-acp-socket-citizen-live.ts +2 -2
- package/scripts/smoke-acp-v2-send-live.ts +33 -18
- package/scripts/smoke-agy-install-state.sh +205 -20
- package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -2
- package/scripts/smoke-meta-install-state.sh +1 -1
- package/scripts/smoke-pi-attach.ts +7 -2
- package/scripts/smoke-user-scope-citizen.sh +177 -0
- package/pi-extensions/lib/entwurf-mailbox-guard.ts +0 -100
- package/scripts/check-entwurf-mailbox-guard.ts +0 -262
|
@@ -16,11 +16,19 @@
|
|
|
16
16
|
* - mode "strict" throws on any error; "collect" (default) returns partial,
|
|
17
17
|
* - errors sorted by filename.
|
|
18
18
|
*
|
|
19
|
+
* #52 adds the store-WIDE half this listing can afford (it already holds every
|
|
20
|
+
* record): two records claiming one `nativeSessionId` are not two citizens. NEITHER
|
|
21
|
+
* is listed — a facts surface may not mint the ownership the certification refuses
|
|
22
|
+
* to mint — both become errors naming each other, and every unrelated citizen keeps
|
|
23
|
+
* listing. Before this, `entwurf_peers` reported a store the doctor calls
|
|
24
|
+
* uncertifiable as clean, which is a wrong FACT, not merely a missing guard.
|
|
25
|
+
*
|
|
19
26
|
* Pure; entries/readRecord injected, no IO.
|
|
20
27
|
*/
|
|
21
28
|
|
|
22
29
|
import assert from "node:assert/strict";
|
|
23
30
|
import {
|
|
31
|
+
type ActiveStoreEntry,
|
|
24
32
|
listAllMetaIdentities,
|
|
25
33
|
type MetaCitizenBackend,
|
|
26
34
|
type MetaIdentity,
|
|
@@ -61,10 +69,17 @@ function reader(map: Record<string, string>): (filename: string) => string {
|
|
|
61
69
|
};
|
|
62
70
|
}
|
|
63
71
|
|
|
72
|
+
/** Store entries from a fixture map — every name a REGULAR file unless `irregular` says
|
|
73
|
+
* otherwise. The listing takes kind, not bare names, so rule 1 (a symlinked record is
|
|
74
|
+
* refused and never followed) is enforceable on this surface too. */
|
|
75
|
+
function entries(map: Record<string, string>, irregular: readonly string[] = []): ActiveStoreEntry[] {
|
|
76
|
+
return Object.keys(map).map((filename) => ({ filename, regularFile: !irregular.includes(filename) }));
|
|
77
|
+
}
|
|
78
|
+
|
|
64
79
|
// ── valid records → identities, sorted ──────────────────────────────────────
|
|
65
80
|
{
|
|
66
81
|
const map = { [`${GID_B}.meta.json`]: rec(GID_B), [`${GID_A}.meta.json`]: rec(GID_A) };
|
|
67
|
-
const { identities, errors } = listAllMetaIdentities(
|
|
82
|
+
const { identities, errors } = listAllMetaIdentities(entries(map), reader(map));
|
|
68
83
|
ok("2 valid → 2 identities, 0 errors", identities.length === 2 && errors.length === 0);
|
|
69
84
|
ok("identities sorted by gardenId", identities[0]?.gardenId === GID_A);
|
|
70
85
|
}
|
|
@@ -72,7 +87,7 @@ function reader(map: Record<string, string>): (filename: string) => string {
|
|
|
72
87
|
// ── corrupt record does NOT blind the listing ───────────────────────────────
|
|
73
88
|
{
|
|
74
89
|
const map = { [`${GID_A}.meta.json`]: rec(GID_A), [`${GID_B}.meta.json`]: "{ broken json" };
|
|
75
|
-
const { identities, errors } = listAllMetaIdentities(
|
|
90
|
+
const { identities, errors } = listAllMetaIdentities(entries(map), reader(map));
|
|
76
91
|
ok("corrupt 1 → valid still listed (not blinded)", identities.length === 1 && identities[0]?.gardenId === GID_A);
|
|
77
92
|
ok("corrupt → exactly 1 error", errors.length === 1);
|
|
78
93
|
ok("error names the corrupt file", errors[0]?.filename === `${GID_B}.meta.json`);
|
|
@@ -81,7 +96,7 @@ function reader(map: Record<string, string>): (filename: string) => string {
|
|
|
81
96
|
// ── body/filename drift → error, excluded ───────────────────────────────────
|
|
82
97
|
{
|
|
83
98
|
const map = { [`${GID_A}.meta.json`]: rec(GID_B) }; // filename A, body claims B
|
|
84
|
-
const { identities, errors } = listAllMetaIdentities(
|
|
99
|
+
const { identities, errors } = listAllMetaIdentities(entries(map), reader(map));
|
|
85
100
|
ok("drift → excluded from identities", identities.length === 0);
|
|
86
101
|
ok("drift → 1 error mentioning drift", errors.length === 1 && /drift/.test(errors[0]?.message ?? ""));
|
|
87
102
|
}
|
|
@@ -92,21 +107,27 @@ function reader(map: Record<string, string>): (filename: string) => string {
|
|
|
92
107
|
if (f === `${GID_B}.meta.json`) throw new Error("ENOENT mid-scan");
|
|
93
108
|
return rec(GID_A);
|
|
94
109
|
};
|
|
95
|
-
const { identities, errors } = listAllMetaIdentities(
|
|
110
|
+
const { identities, errors } = listAllMetaIdentities(
|
|
111
|
+
[
|
|
112
|
+
{ filename: `${GID_A}.meta.json`, regularFile: true },
|
|
113
|
+
{ filename: `${GID_B}.meta.json`, regularFile: true },
|
|
114
|
+
],
|
|
115
|
+
read,
|
|
116
|
+
);
|
|
96
117
|
ok("reader throw → error, valid still listed", identities.length === 1 && errors.length === 1);
|
|
97
118
|
}
|
|
98
119
|
|
|
99
120
|
// ── non-.meta.json ignored ──────────────────────────────────────────────────
|
|
100
121
|
{
|
|
101
122
|
const map = { [`${GID_A}.meta.json`]: rec(GID_A), "README.txt": "x", "inbox.signal": "y" };
|
|
102
|
-
const { identities, errors } = listAllMetaIdentities(
|
|
123
|
+
const { identities, errors } = listAllMetaIdentities(entries(map), reader(map));
|
|
103
124
|
ok("non-.meta.json entries ignored", identities.length === 1 && errors.length === 0);
|
|
104
125
|
}
|
|
105
126
|
|
|
106
127
|
// ── verbatim-or-nothing: error keyset = {filename, message} ─────────────────
|
|
107
128
|
{
|
|
108
129
|
const map = { [`${GID_A}.meta.json`]: "{ broken" };
|
|
109
|
-
const { errors } = listAllMetaIdentities(
|
|
130
|
+
const { errors } = listAllMetaIdentities(entries(map), reader(map));
|
|
110
131
|
const keys = Object.keys(errors[0] ?? {}).sort();
|
|
111
132
|
assert.deepStrictEqual(keys, ["filename", "message"], `error keyset drift: ${keys.join(",")}`);
|
|
112
133
|
ok("error verbatim-or-nothing (filename + message only, no identity field)", true);
|
|
@@ -117,20 +138,81 @@ function reader(map: Record<string, string>): (filename: string) => string {
|
|
|
117
138
|
const map = { [`${GID_A}.meta.json`]: rec(GID_A), [`${GID_B}.meta.json`]: "{ broken" };
|
|
118
139
|
let threw = false;
|
|
119
140
|
try {
|
|
120
|
-
listAllMetaIdentities(
|
|
141
|
+
listAllMetaIdentities(entries(map), reader(map), { mode: "strict" });
|
|
121
142
|
} catch {
|
|
122
143
|
threw = true;
|
|
123
144
|
}
|
|
124
145
|
ok("mode strict → throw if any record unreadable", threw);
|
|
125
|
-
const { identities } = listAllMetaIdentities(
|
|
146
|
+
const { identities } = listAllMetaIdentities(entries(map), reader(map), { mode: "collect" });
|
|
126
147
|
ok("mode collect (default) → partial, no throw", identities.length === 1);
|
|
127
148
|
}
|
|
128
149
|
|
|
129
150
|
// ── errors sorted by filename ───────────────────────────────────────────────
|
|
130
151
|
{
|
|
131
152
|
const map = { [`${GID_B}.meta.json`]: "{ x", [`${GID_A}.meta.json`]: "{ y" };
|
|
132
|
-
const { errors } = listAllMetaIdentities(
|
|
153
|
+
const { errors } = listAllMetaIdentities(entries(map), reader(map));
|
|
133
154
|
ok("errors sorted by filename", errors[0]?.filename === `${GID_A}.meta.json`);
|
|
134
155
|
}
|
|
135
156
|
|
|
157
|
+
// ── #52: duplicate nativeSessionId is not two citizens ──────────────────────
|
|
158
|
+
{
|
|
159
|
+
const shared = "n-shared-conversation";
|
|
160
|
+
const map = {
|
|
161
|
+
[`${GID_A}.meta.json`]: rec(GID_A, { nativeSessionId: shared }),
|
|
162
|
+
[`${GID_B}.meta.json`]: rec(GID_B, { nativeSessionId: shared }),
|
|
163
|
+
};
|
|
164
|
+
const { identities, errors } = listAllMetaIdentities(entries(map), reader(map));
|
|
165
|
+
ok("duplicate nativeSessionId → NEITHER record is listed as a citizen", identities.length === 0);
|
|
166
|
+
ok("duplicate → one error per rival (both are told)", errors.length === 2);
|
|
167
|
+
ok(
|
|
168
|
+
"each error names the OTHER holder, so the pair is debuggable from either side",
|
|
169
|
+
(errors[0]?.message ?? "").includes(`${GID_B}.meta.json`) &&
|
|
170
|
+
(errors[1]?.message ?? "").includes(`${GID_A}.meta.json`),
|
|
171
|
+
);
|
|
172
|
+
ok(
|
|
173
|
+
"the duplicate error names the fresh-cut verb (one prescription, as everywhere else)",
|
|
174
|
+
errors.every((e) => e.message.includes("meta-bridge-fresh-cut")),
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── #52: a duplicate must not blind the citizens around it ──────────────────
|
|
179
|
+
{
|
|
180
|
+
const shared = "n-shared-conversation";
|
|
181
|
+
const GID_C = "20260611T333333-cccccc";
|
|
182
|
+
const map = {
|
|
183
|
+
[`${GID_A}.meta.json`]: rec(GID_A, { nativeSessionId: shared }),
|
|
184
|
+
[`${GID_B}.meta.json`]: rec(GID_B, { nativeSessionId: shared }),
|
|
185
|
+
[`${GID_C}.meta.json`]: rec(GID_C),
|
|
186
|
+
};
|
|
187
|
+
const { identities, errors } = listAllMetaIdentities(entries(map), reader(map));
|
|
188
|
+
ok(
|
|
189
|
+
"an unrelated citizen still lists beside a duplicate pair (0.10 lesson holds)",
|
|
190
|
+
identities.length === 1 && identities[0]?.gardenId === GID_C,
|
|
191
|
+
);
|
|
192
|
+
ok("only the two rivals become errors", errors.length === 2);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── #52: strict mode refuses a store with a duplicate ───────────────────────
|
|
196
|
+
{
|
|
197
|
+
const shared = "n-shared-conversation";
|
|
198
|
+
const map = {
|
|
199
|
+
[`${GID_A}.meta.json`]: rec(GID_A, { nativeSessionId: shared }),
|
|
200
|
+
[`${GID_B}.meta.json`]: rec(GID_B, { nativeSessionId: shared }),
|
|
201
|
+
};
|
|
202
|
+
let threw = false;
|
|
203
|
+
try {
|
|
204
|
+
listAllMetaIdentities(entries(map), reader(map), { mode: "strict" });
|
|
205
|
+
} catch {
|
|
206
|
+
threw = true;
|
|
207
|
+
}
|
|
208
|
+
ok("mode strict → a duplicate throws like any other unreadable store", threw);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── #52: one holder is not a duplicate (no false positive) ──────────────────
|
|
212
|
+
{
|
|
213
|
+
const map = { [`${GID_A}.meta.json`]: rec(GID_A), [`${GID_B}.meta.json`]: rec(GID_B) };
|
|
214
|
+
const { identities, errors } = listAllMetaIdentities(entries(map), reader(map));
|
|
215
|
+
ok("distinct nativeSessionIds are untouched by the uniqueness pass", identities.length === 2 && errors.length === 0);
|
|
216
|
+
}
|
|
217
|
+
|
|
136
218
|
console.log(`\n[check-meta-listing] ${passed} assertions ok`);
|
|
@@ -107,6 +107,60 @@ ok(
|
|
|
107
107
|
readMetaReceiverMarker({ gardenId: ghostGarden, receiversDir: DIR, verifyOwner: false }) !== null,
|
|
108
108
|
);
|
|
109
109
|
|
|
110
|
+
// ── the owner CLAIM, one layer above liveness (#53 A) ───────────────────────
|
|
111
|
+
// A marker naming pid 1 passes the pid-reuse guard on a running Linux host: init is
|
|
112
|
+
// still the very process the marker named, so its start-key matches. The claim is
|
|
113
|
+
// what is false — init owns no Claude session. Such a marker is legacy or corrupt
|
|
114
|
+
// residue (any pre-fix writer that was reparented to init; a foreign/damaged file);
|
|
115
|
+
// the one observed was a shell-form hook. Honoring it made a dead garden id read as
|
|
116
|
+
// an ACTIVE RECEIVER and, one surface over, left meta-bridge-fresh-cut unable to be
|
|
117
|
+
// unblocked by the action it prescribes (#53 A).
|
|
118
|
+
const initGarden = "20260614T130000-cccccc";
|
|
119
|
+
const initKey = processStartKey(1);
|
|
120
|
+
ok("this host CAN read a start key for pid 1 (the residue cells are not vacuous)", initKey !== "");
|
|
121
|
+
writeFileSync(
|
|
122
|
+
metaReceiverMarkerPath(initGarden, DIR),
|
|
123
|
+
`${JSON.stringify({
|
|
124
|
+
gardenId: initGarden,
|
|
125
|
+
backend: "claude-code",
|
|
126
|
+
nativeSessionId: "n-init",
|
|
127
|
+
ownerPid: 1,
|
|
128
|
+
ownerStartKey: initKey, // the REAL key: the pid-reuse guard would pass
|
|
129
|
+
ownerKind: "claude-code-cli",
|
|
130
|
+
armProvenance: "session-start",
|
|
131
|
+
updatedAt: "2026-06-10T16:03:10.000Z",
|
|
132
|
+
})}\n`,
|
|
133
|
+
);
|
|
134
|
+
ok(
|
|
135
|
+
"an init-owned marker reads as null — pid 1 cannot own a session, however live init is",
|
|
136
|
+
readMetaReceiverMarker({ gardenId: initGarden, receiversDir: DIR }) === null,
|
|
137
|
+
);
|
|
138
|
+
ok(
|
|
139
|
+
"the classifier itself still says LIVE for it — the refusal is a CLAIM rule, not a liveness one",
|
|
140
|
+
classifyMarkerOwner(initKey, { currentStartKey: processStartKey(1), pidExists: probePidExistence(1) }) === "live",
|
|
141
|
+
);
|
|
142
|
+
ok(
|
|
143
|
+
"verifyOwner:false does NOT reach past plausibility (it opts out of liveness, not of validity)",
|
|
144
|
+
readMetaReceiverMarker({ gardenId: initGarden, receiversDir: DIR, verifyOwner: false }) === null,
|
|
145
|
+
);
|
|
146
|
+
let initWriteRejected = false;
|
|
147
|
+
try {
|
|
148
|
+
writeMetaReceiverMarker({
|
|
149
|
+
gardenId: initGarden,
|
|
150
|
+
backend: "claude-code",
|
|
151
|
+
nativeSessionId: "n-init",
|
|
152
|
+
ownerPid: 1,
|
|
153
|
+
armProvenance: "session-start",
|
|
154
|
+
receiversDir: DIR,
|
|
155
|
+
});
|
|
156
|
+
} catch {
|
|
157
|
+
initWriteRejected = true;
|
|
158
|
+
}
|
|
159
|
+
ok(
|
|
160
|
+
"minting an init-owned receiver marker THROWS at the write boundary (no writer can reintroduce it)",
|
|
161
|
+
initWriteRejected,
|
|
162
|
+
);
|
|
163
|
+
|
|
110
164
|
// ── armProvenance constraint ────────────────────────────────────────────────
|
|
111
165
|
ok(
|
|
112
166
|
"arm provenances are exactly the arm-capable events",
|
|
@@ -211,7 +211,7 @@ async function makeHarness(opts: MakeHarnessOpts = {}): Promise<Harness> {
|
|
|
211
211
|
const NATIVE_A: ModelLike = { provider: "openai-codex", id: "gpt-5.4" };
|
|
212
212
|
const NATIVE_B: ModelLike = { provider: "openai-codex", id: "gpt-5.5" };
|
|
213
213
|
const PSA_SONNET: ModelLike = { provider: "entwurf", id: "claude-sonnet-5" };
|
|
214
|
-
const PSA_OPUS: ModelLike = { provider: "entwurf", id: "claude-opus-
|
|
214
|
+
const PSA_OPUS: ModelLike = { provider: "entwurf", id: "claude-opus-5" };
|
|
215
215
|
|
|
216
216
|
let passed = 0;
|
|
217
217
|
let failed = 0;
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* Backticks and `$(...)` inside a user prompt got executed by the remote shell
|
|
7
7
|
* before pi ever saw them. The fix introduced `shellQuote()` (POSIX `'...'`
|
|
8
8
|
* with `'\''` escape) in two places:
|
|
9
|
-
* - pi-extensions/lib/entwurf-core.ts (
|
|
9
|
+
* - pi-extensions/lib/entwurf-core.ts (the one remaining site; the entwurf.ts /
|
|
10
|
+
* entwurf-async.ts siblings this once cross-checked were removed in 0.12)
|
|
10
11
|
*
|
|
11
12
|
* This script enforces two invariants:
|
|
12
13
|
* 1. `shellQuote()` source matches the reference implementation byte-for-byte.
|