@mcpherson-ai/observa-local-node 0.1.3

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 (34) hide show
  1. package/README.md +103 -0
  2. package/SHA256SUMS +33 -0
  3. package/artifacts/mcphersonai-observa-adapter-n8n-0.1.0.tgz +0 -0
  4. package/artifacts/mcphersonai-observa-domain-generic-0.1.0.tgz +0 -0
  5. package/artifacts/mcphersonai-observa-hosted-transport-0.1.0.tgz +0 -0
  6. package/artifacts/mcphersonai-observa-n8n-h1-binding-0.1.0.tgz +0 -0
  7. package/artifacts/mcphersonai-observa-node-0.1.0.tgz +0 -0
  8. package/distribution/observa-cli/bin/observa-n8n-hosted.mjs +114 -0
  9. package/distribution/observa-cli/bin/observa.mjs +331 -0
  10. package/distribution/observa-cli/integrations/n8n/observa-external-hook.cjs +132 -0
  11. package/distribution/observa-cli/keys/observa-beta-1.public.json +7 -0
  12. package/distribution/observa-cli/src/artifact.mjs +143 -0
  13. package/distribution/observa-cli/src/config.mjs +162 -0
  14. package/distribution/observa-cli/src/errors.mjs +21 -0
  15. package/distribution/observa-cli/src/hosted-delivery.mjs +148 -0
  16. package/distribution/observa-cli/src/index.mjs +39 -0
  17. package/distribution/observa-cli/src/install.mjs +443 -0
  18. package/distribution/observa-cli/src/lock.mjs +57 -0
  19. package/distribution/observa-cli/src/manifest-schema.mjs +208 -0
  20. package/distribution/observa-cli/src/manifest-verify.mjs +122 -0
  21. package/distribution/observa-cli/src/n8n-hook.mjs +70 -0
  22. package/distribution/observa-cli/src/pair.mjs +149 -0
  23. package/distribution/observa-cli/src/re-pair.mjs +171 -0
  24. package/distribution/observa-cli/src/service.mjs +218 -0
  25. package/distribution/observa-cli/src/state.mjs +87 -0
  26. package/distribution/observa-cli/src/status.mjs +191 -0
  27. package/distribution/observa-cli/src/vocabulary.mjs +47 -0
  28. package/npm-distribution-provenance.json +1 -0
  29. package/package.json +36 -0
  30. package/runtime-adapters/n8n/src/strict-json.mjs +138 -0
  31. package/sdk/contracts/canonical.mjs +289 -0
  32. package/sdk/contracts/entry-boundary.mjs +417 -0
  33. package/sdk/contracts/errors.mjs +334 -0
  34. package/sdk/contracts/stable-primitives.mjs +141 -0
@@ -0,0 +1,417 @@
1
+ // THE PUBLIC ENTRY BOUNDARY. One mechanism, installed over the declared public
2
+ // surface, holding one invariant:
3
+ //
4
+ // NO PUBLIC H1 ENTRY POINT MAY EXECUTE CALLER-CONTROLLED ACCESSOR,
5
+ // DESTRUCTURING OR COERCION CODE BEFORE THE CLOSED REFUSAL BOUNDARY.
6
+ //
7
+ // WHY THIS FILE EXISTS RATHER THAN THIRTY PATCHES.
8
+ //
9
+ // Round 12B closed six entry points that let a raw `TypeError` escape, wrapped
10
+ // them in `refuseOnRawThrow`, and pinned seven hand-picked sites with a test
11
+ // named for a universal property. Independent review #1 then found roughly
12
+ // THIRTY public entry points with the same shape, and the reason is structural
13
+ // rather than clerical: the seam is the PARAMETER LIST.
14
+ //
15
+ // adoptAuthorityBinding({ tenantLocalId, boundIntentId, authorityBindingId } = {})
16
+ //
17
+ // Destructuring in a parameter list runs `[[Get]]` on the caller's object
18
+ // BEFORE the first statement of the function body — so before any guard, any
19
+ // snapshot, any `refuseOnRawThrow`. A getter runs there. A Proxy `get` trap runs
20
+ // there. `...rest` runs `ownKeys` there. A revoked Proxy throws there. The
21
+ // escaping error carries THE CALLER'S OWN MESSAGE TEXT, which is the leak
22
+ // `errors.mjs` opens by forbidding — a probe put a subject-identifying string in
23
+ // a getter's `TypeError` and read it back out of a public method.
24
+ //
25
+ // No amount of guarding INSIDE the body can close a seam that fires before the
26
+ // body. The only fix that generalizes is to stop the body from ever seeing the
27
+ // caller's object: materialize the argument record at the boundary, and hand the
28
+ // body a frozen, null-prototype, data-only clone. Then the existing
29
+ // destructuring in every method is safe by construction, because there is
30
+ // nothing left to trap.
31
+ //
32
+ // TWO LEVERS, BOTH MECHANICAL:
33
+ //
34
+ // `entryRecord` materializes ONE argument record inside a closed refusal
35
+ // boundary. Accessors are REFUSED, never invoked; symbol
36
+ // keys, non-plain prototypes, arrays and strings are
37
+ // refused; no value is coerced.
38
+ //
39
+ // `entryArray` the same lever for the ARRAY carrier. Added by review #1
40
+ // (H1R-01/02/05), which found the record half of this
41
+ // boundary complete and the array half missing entirely:
42
+ // three public exports validated an array with
43
+ // `Array.isArray` and then CONSUMED it through `for...of`
44
+ // or `.length`/`[i]`, both of which a caller controls.
45
+ //
46
+ // `sealEntryPoints` installs `entryRecord` over a declared method table, in
47
+ // one call, so a method cannot be left out by forgetting
48
+ // to edit it. The table is compared against the contract's
49
+ // own method list by test, so a method added to the surface
50
+ // without a boundary code fails rather than shipping bare.
51
+ //
52
+ // WHAT `entryRecord` DELIBERATELY DOES NOT DO. It is SHALLOW. Members keep their
53
+ // identity — a function stays a function, a nested record stays a reference —
54
+ // because the entry points that take functions (`registerDomainProfile`, and
55
+ // every `clock`) could not otherwise be sealed at all, and because each member
56
+ // already has its own validator that snapshots to the depth that member needs.
57
+ // The boundary's job is to make sure the BODY never touches caller-controlled
58
+ // STRUCTURE; the validators' job is to make sure each MEMBER is what it claims.
59
+ // ROUND-2 REVIEW #1, H1R2-01 — WRAPPING THE WALK WAS NOT WRAPPING THE TRAPS.
60
+ //
61
+ // Every `Reflect.ownKeys`, `getOwnPropertyDescriptor` and `getPrototypeOf`
62
+ // below EXECUTES CALLER CODE. Round 12B put the whole walk inside
63
+ // `refuseOnRawThrow`, which closed the raw-`TypeError` half; it did not close
64
+ // the other half, because a trap that raised a publicly constructed
65
+ // `SdkContractError` was rethrown unchanged with the caller's own text in it.
66
+ //
67
+ // Each trap-invoking primitive now runs inside `runUntrusted`, which is the
68
+ // smallest dynamic extent that is genuinely foreign code. Contract refusals in
69
+ // the loop body sit OUTSIDE those regions and keep their own codes; anything a
70
+ // trap raises is re-coded. `errors.mjs` states the provenance model.
71
+ import { types as nodeTypes } from "node:util";
72
+ import { refuse, runUntrusted } from "./errors.mjs";
73
+ import { stableStringHas } from "./stable-primitives.mjs";
74
+
75
+ // The installer makes a security boundary durable. Capture the ordinary
76
+ // object operations it needs so a later ambient replacement cannot turn the
77
+ // commit phase back into a caller-controlled dispatch point. A Proxy cannot
78
+ // offer transactional property definition, so it is rejected before any
79
+ // target inspection or mutation.
80
+ const isProxy = nodeTypes.isProxy;
81
+ const objectDefineProperty = Object.defineProperty;
82
+ const objectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
83
+ const objectKeys = Object.keys;
84
+ const objectFreeze = Object.freeze;
85
+ const reflectApply = Reflect.apply;
86
+ const arrayIsArray = Array.isArray;
87
+ const NativeArray = Array;
88
+ const functionToString = Function.prototype.toString;
89
+
90
+ // A shared frozen empty record, so `undefined` keeps meaning "no arguments" —
91
+ // the behaviour the `= {}` parameter defaults used to provide, without the
92
+ // default-parameter evaluation those defaults performed.
93
+ const NO_ARGUMENTS = Object.freeze(Object.create(null));
94
+
95
+ // THE STRUCTURAL CODE POLICY, stated once for both levers.
96
+ //
97
+ // Three facts are decided identically wherever structure is inspected — "the
98
+ // prototype is not plain", "an unsafe or non-addressable key was found", "an
99
+ // accessor was found where a data property was required" — and by default they
100
+ // carry `canonical.mjs`'s own three codes, so one condition has one name across
101
+ // the whole SDK.
102
+ //
103
+ // A boundary that already publishes its OWN closed code for those facts passes
104
+ // `structuralCode` and gets it. That is not drift: it is the rule
105
+ // `text-safety.mjs` states for the same situation — "this append is malformed",
106
+ // "this history is malformed" and "this envelope is malformed" are different
107
+ // facts about different boundaries and each keeps its own code, but they can
108
+ // never disagree about WHICH value is unsafe, because one implementation
109
+ // decides. `projection.mjs` has published `PROJECTION_EVENT_INVALID` for an
110
+ // accessor on an event since round 10 and keeps it; the node's sealed methods
111
+ // have published `VALUE_ACCESSOR_REFUSED` and keep that.
112
+ //
113
+ // IT IS A PLAIN STRING PARAMETER, NOT AN OPTIONS RECORD, and the reason is this
114
+ // file's own first sentence. The first draft wrote it as
115
+ // `{ structuralCode = null } = {}` — DESTRUCTURING IN A PARAMETER LIST, the
116
+ // exact seam `entryRecord` exists to close, reintroduced into the boundary
117
+ // itself. `entry-position-sweep.test.mjs` caught it on its first run:
118
+ // `entryRecord(x, code, null)` raised a raw TypeError, and a Proxy third
119
+ // argument leaked the caller's own message text straight back out. A scalar has
120
+ // no structure to trap.
121
+ function structuralCodes(structuralCode) {
122
+ return {
123
+ notPlain: structuralCode ?? "VALUE_NOT_PLAIN",
124
+ unsafeKey: structuralCode ?? "VALUE_UNSAFE_KEY",
125
+ accessor: structuralCode ?? "VALUE_ACCESSOR_REFUSED",
126
+ hole: structuralCode ?? "VALUE_NOT_JSON_COMPATIBLE",
127
+ };
128
+ }
129
+
130
+ // Materialize a caller-supplied argument record.
131
+ //
132
+ // Null-prototype, so a field lookup can never reach `Object.prototype` and
133
+ // answer with an inherited function — the shape that turns `record[name]` into
134
+ // an execution seam. Frozen, so nothing downstream can write back into it.
135
+ export function entryRecord(rawArguments, code, structuralCode) {
136
+ const CODES = structuralCodes(structuralCode);
137
+ if (rawArguments === undefined) return NO_ARGUMENTS;
138
+ // A string is an object-ish thing with indexed own properties, and reading
139
+ // one as a record silently yields a per-character map rather than failing.
140
+ if (typeof rawArguments === "string") refuse(code);
141
+ if (rawArguments === null || typeof rawArguments !== "object" ||
142
+ runUntrusted(code, () => arrayIsArray(rawArguments))) {
143
+ refuse(code);
144
+ }
145
+ // Prototype first: a class instance or a `Proxy` over one can carry
146
+ // accessors on its prototype that plain-object callers never have. Reading
147
+ // the prototype of a revoked Proxy throws, which the wrapper closes.
148
+ // THE STRUCTURAL CODES ARE canonical.mjs's OWN, NOT THE CALLER'S.
149
+ //
150
+ // "this is not an argument record at all" is a fact about THIS boundary and
151
+ // takes the caller's code. "an accessor was found where a data property was
152
+ // required", "a symbol key was found", "the prototype is not plain" are the
153
+ // same three facts `dataRecordEntries` already names, and this file answers
154
+ // them with the same three codes. One condition, one code, wherever it is
155
+ // detected — the drift this codebase has paid for three times was always two
156
+ // places describing one rule slightly differently.
157
+ const prototype = runUntrusted(code, () => Object.getPrototypeOf(rawArguments));
158
+ if (prototype !== Object.prototype && prototype !== null) refuse(CODES.notPlain);
159
+ const record = Object.create(null);
160
+ const keys = runUntrusted(code, () => Reflect.ownKeys(rawArguments));
161
+ for (let index = 0; index < keys.length; index += 1) {
162
+ const key = keys[index];
163
+ // A symbol-keyed field is not addressable by any declared parameter, so
164
+ // it can only be a channel. Refused rather than dropped: dropping would
165
+ // mean the record validated is not the record the caller handed over.
166
+ if (typeof key !== "string") refuse(CODES.unsafeKey);
167
+ const descriptor = runUntrusted(code, () => Object.getOwnPropertyDescriptor(rawArguments, key));
168
+ // A Proxy may legally report a key from `ownKeys` and then answer
169
+ // `getOwnPropertyDescriptor` with `undefined` for it. Reading `"value" in
170
+ // undefined` raised a raw TypeError out of `projectStateAt` (review #1,
171
+ // H1R-01); a key that will not describe itself is refused here instead.
172
+ if (descriptor === undefined) refuse(code);
173
+ // THE ACCESSOR IS REFUSED WITHOUT BEING INVOKED. `"value" in descriptor`
174
+ // asks the question `descriptor.get` would answer by RUNNING. A
175
+ // non-enumerable data property is refused by the same rule and for the
176
+ // same reason `dataRecordEntries` refuses it: it is invisible to
177
+ // destructuring and visible to `Reflect.ownKeys`, so copying it would make
178
+ // the body see a field the caller's own spread would not.
179
+ if (!("value" in descriptor) || !descriptor.enumerable) refuse(CODES.accessor);
180
+ // THE DESCRIPTOR'S VALUE, NEVER `rawArguments[key]`. A Proxy can present a
181
+ // perfectly ordinary DATA descriptor and still answer `get` with something
182
+ // else, and answer it differently on every read — which is how the same
183
+ // argument object produced three different projections (review #1,
184
+ // H1R-01). Reading the descriptor is the only read that cannot be
185
+ // re-answered.
186
+ record[key] = descriptor.value;
187
+ }
188
+ return objectFreeze(record);
189
+ }
190
+
191
+ // Bound so a hostile oversized carrier is refused before it is walked or
192
+ // copied. A contract limit, not a tuning knob — the same posture `canonical.mjs`
193
+ // takes toward depth, node count and string length.
194
+ const MAX_ENTRY_ARRAY_LENGTH = 100_000;
195
+
196
+ // Materialize a caller-supplied ARRAY argument.
197
+ //
198
+ // WHY THE ARRAY CARRIER NEEDED ITS OWN LEVER. `Array.isArray` answers one
199
+ // question — "is the carrier an Array" — and every way of then CONSUMING that
200
+ // array is caller-controlled:
201
+ //
202
+ // `for...of` dispatches `value[Symbol.iterator]`, and a plain array may
203
+ // own one. `assertAdapterProvides(adapter, ["EMIT_..."])` with
204
+ // an own iterator yielding a DIFFERENT capability returned
205
+ // `true` for a capability the adapter never declared — the
206
+ // gate certified a sequence it had not validated.
207
+ // `.length`/`[i]` are `[[Get]]`s, which a Proxy over an array traps, so both
208
+ // could throw the caller's own message text out of
209
+ // `projectStateAt`.
210
+ //
211
+ // So the rule is the same one `entryRecord` holds, applied to indices:
212
+ // VALIDATE ONCE, CAPTURE ONCE, USE THE CAPTURE. Elements are read from their
213
+ // DESCRIPTORS in a single `Reflect.ownKeys` walk, and the frozen dense copy
214
+ // returned is the only thing any caller may go on to consume.
215
+ export function entryArray(rawArguments, code, structuralCode, maxLength = MAX_ENTRY_ARRAY_LENGTH) {
216
+ const CODES = structuralCodes(structuralCode);
217
+ // `Array.isArray` is not a pure predicate: it THROWS on a revoked Proxy.
218
+ // It runs inside the wrapper for the same reason `snapshotRecord`'s does.
219
+ if (!runUntrusted(code, () => arrayIsArray(rawArguments))) refuse(code);
220
+ // Plain arrays only, exactly as `canonical.mjs`'s `arrayElements` requires:
221
+ // a subclass could carry a prototype method that consumption dispatches
222
+ // through.
223
+ if (runUntrusted(code, () => Object.getPrototypeOf(rawArguments)) !== Array.prototype) {
224
+ refuse(CODES.notPlain);
225
+ }
226
+ const byIndex = new Map();
227
+ const keys = runUntrusted(code, () => Reflect.ownKeys(rawArguments));
228
+ for (let keyIndex = 0; keyIndex < keys.length; keyIndex += 1) {
229
+ const key = keys[keyIndex];
230
+ // A SYMBOL KEY ON AN ARRAY IS SKIPPED, NOT REFUSED — and the asymmetry
231
+ // with `entryRecord` is deliberate. On a record a symbol key is a channel
232
+ // carrying a value the body might read. On an array it can never be an
233
+ // indexed element, so it carries nothing this boundary reads; the one
234
+ // symbol a caller realistically attaches is `Symbol.iterator`, and the
235
+ // contract's answer to a hostile iterator is that IT IS NEVER CONSULTED,
236
+ // not that the array is rejected. `sdk-export-contract.test.mjs` has
237
+ // pinned that acceptance since round 10 and it stays true.
238
+ if (typeof key === "symbol") continue;
239
+ if (key === "length") continue;
240
+ if (!ARRAY_INDEX.test(key)) refuse(CODES.unsafeKey);
241
+ const descriptor = runUntrusted(code, () => Object.getOwnPropertyDescriptor(rawArguments, key));
242
+ if (descriptor === undefined) refuse(code);
243
+ if (!("value" in descriptor) || !descriptor.enumerable) refuse(CODES.accessor);
244
+ byIndex.set(Number(key), descriptor.value);
245
+ if (byIndex.size > maxLength) refuse(code);
246
+ }
247
+ // Dense, built from the CAPTURED indices rather than from a second read of
248
+ // `length` — which is itself a trappable `[[Get]]`. A hole would read as
249
+ // `undefined`, which is not a value any caller of this boundary accepts.
250
+ const elements = new NativeArray(byIndex.size);
251
+ for (let index = 0; index < byIndex.size; index += 1) {
252
+ if (!byIndex.has(index)) refuse(CODES.hole);
253
+ elements[index] = byIndex.get(index);
254
+ }
255
+ return objectFreeze(elements);
256
+ }
257
+
258
+ const ARRAY_INDEX = /^(?:0|[1-9][0-9]*)$/u;
259
+
260
+ // HOW MANY ARGUMENT SLOTS A FUNCTION ACTUALLY DECLARES.
261
+ //
262
+ // NOT `Function.length`, which stops counting at the first defaulted parameter —
263
+ // so `method({ a, b } = {})` reports 0 and `method(raw)` reports 1, and a
264
+ // wrapper that dropped a second argument would look identical to one that did
265
+ // not. The parameter list is read from the source text and split on TOP-LEVEL
266
+ // commas, so a destructured or defaulted parameter counts as one slot.
267
+ //
268
+ // It lives here rather than in a test because `sealEntryPoints` is the last
269
+ // moment the UNSEALED function exists: after installation every method reports
270
+ // the wrapper's single parameter, and the number this measures is gone
271
+ // (round-2 review #1, H1R2-09).
272
+ function declaredParameterCount(fn) {
273
+ const source = reflectApply(functionToString, fn, []);
274
+ const start = source.indexOf("(");
275
+ if (start < 0) return 0;
276
+ let depth = 0;
277
+ let params = "";
278
+ for (let index = start; index < source.length; index += 1) {
279
+ const char = source[index];
280
+ if (stableStringHas("([{", char)) depth += 1;
281
+ if (stableStringHas(")]}", char)) {
282
+ depth -= 1;
283
+ if (depth === 0) break;
284
+ }
285
+ if (depth >= 1) params += char;
286
+ }
287
+ let inner = 0;
288
+ let slots = 1;
289
+ let seen = false;
290
+ for (const char of params.slice(1)) {
291
+ if (stableStringHas("([{", char)) inner += 1;
292
+ else if (stableStringHas(")]}", char)) inner -= 1;
293
+ else if (char === "," && inner === 0) slots += 1;
294
+ if (!/\s/u.test(char)) seen = true;
295
+ }
296
+ return seen ? slots : 0;
297
+ }
298
+
299
+ // Install the boundary over a declared method table.
300
+ //
301
+ // `codes` maps method name to the refusal code that method's malformed
302
+ // arguments produce, so a caller still gets the boundary-specific code its
303
+ // sibling validators use rather than one generic entry-point code.
304
+ //
305
+ // The wrapper is the ONLY thing that touches the caller's object; the wrapped
306
+ // method receives the materialized record and may destructure it freely.
307
+ // Returns the frozen table so a test can prove the installed set IS the
308
+ // declared public surface, rather than trusting that the two were kept level.
309
+ // ROUND-2 REVIEW #1, H1R2-07 — THE INSTALLER WAS OUTSIDE THE BOUNDARY IT
310
+ // INSTALLS, AND IT INSTALLED NON-ATOMICALLY.
311
+ //
312
+ // Two defects, one shape. `Object.entries(codes)` ran the caller's accessors
313
+ // before any guard, so a getter on the codes table raised a RAW `TypeError`
314
+ // carrying the caller's own text out of a public export — the exact seam this
315
+ // file's first sentence forbids, in the function that exists to close it. And
316
+ // the loop mutated the prototype AS IT WENT, so a refusal partway through the
317
+ // table left the prototype HALF-SEALED: some methods behind the boundary, some
318
+ // bare, and a returned attestation naming methods that were never wrapped. A
319
+ // partially installed security boundary is worse than an absent one, because
320
+ // the attestation says it is present.
321
+ //
322
+ // Both close the same way the rest of this file works: MATERIALIZE, VALIDATE
323
+ // EVERYTHING, THEN ACT. The codes table is materialized by `entryRecord`, every
324
+ // entry is resolved and checked before a single property is redefined, and the
325
+ // install loop touches the prototype only once the whole table is known good.
326
+ //
327
+ // The pre-seal arity is captured here as well, because it is the only place it
328
+ // still exists: after installation every method reports the wrapper's arity of
329
+ // 1, so a test reading the prototype can no longer observe what the method
330
+ // itself declared (H1R2-09).
331
+ export function sealEntryPoints(rawPrototype, rawCodes) {
332
+ // A Proxy may execute arbitrary traps during descriptor inspection or claim
333
+ // a successful define without making the write. There is no rollback-safe
334
+ // commit over that protocol, so reject it before even validating the table.
335
+ if (rawPrototype === null || typeof rawPrototype !== "object" || isProxy(rawPrototype)) {
336
+ refuse("ENTRY_POINT_NOT_DECLARED");
337
+ }
338
+ const codes = entryRecord(rawCodes, "ENTRY_POINT_NOT_DECLARED");
339
+ // PHASE 1 — resolve and validate the WHOLE table. Nothing is mutated here.
340
+ const names = objectKeys(codes);
341
+ const planned = new Array(names.length);
342
+ for (let index = 0; index < names.length; index += 1) {
343
+ const name = names[index];
344
+ const code = codes[name];
345
+ if (typeof code !== "string") refuse("ENTRY_POINT_NOT_DECLARED");
346
+ const descriptor = runUntrusted(
347
+ "ENTRY_POINT_NOT_DECLARED",
348
+ () => objectGetOwnPropertyDescriptor(rawPrototype, name),
349
+ );
350
+ // A non-configurable slot cannot be replaced reliably. Reject all such
351
+ // tables during phase 1, before a sibling slot has been touched.
352
+ if (!descriptor || typeof descriptor.value !== "function" || descriptor.configurable !== true) {
353
+ refuse("ENTRY_POINT_NOT_DECLARED");
354
+ }
355
+ planned[index] = {
356
+ name, code, descriptor,
357
+ inner: descriptor.value,
358
+ arity: declaredParameterCount(descriptor.value),
359
+ };
360
+ }
361
+ // Nothing in the ordinary-object commit phase can run caller code. Check
362
+ // once more after all planning work so a target altered during validation is
363
+ // refused before its first sibling is installed. (Proxies were rejected
364
+ // above because JavaScript cannot make this guarantee for their traps.)
365
+ for (let index = 0; index < planned.length; index += 1) {
366
+ const { name, descriptor } = planned[index];
367
+ const current = objectGetOwnPropertyDescriptor(rawPrototype, name);
368
+ if (!current || current.value !== descriptor.value ||
369
+ current.enumerable !== descriptor.enumerable ||
370
+ current.configurable !== descriptor.configurable ||
371
+ current.writable !== descriptor.writable) {
372
+ refuse("ENTRY_POINT_NOT_DECLARED");
373
+ }
374
+ }
375
+ // PHASE 2 — install. Every entry is already known good, so this cannot refuse
376
+ // partway and cannot leave the prototype in a mixed state.
377
+ const arities = Object.create(null);
378
+ const installedCodes = Object.create(null);
379
+ for (let index = 0; index < planned.length; index += 1) {
380
+ const { name, code, descriptor, inner, arity } = planned[index];
381
+ // Defined through a computed-name literal so the sealed function keeps the
382
+ // method's own name. The surface guard reads names off the prototype, and a
383
+ // wrapper reporting `name: ""` would make every stack trace and every
384
+ // reflective check read as anonymous.
385
+ const sealed = {
386
+ [name](rawArguments) {
387
+ return reflectApply(inner, this, [entryRecord(rawArguments, code)]);
388
+ },
389
+ }[name];
390
+ // The wrapper itself is the entry-security boundary. It cannot remain
391
+ // writable/configurable after attestation or a later assignment could
392
+ // restore a raw caller-facing method.
393
+ objectDefineProperty(rawPrototype, name, {
394
+ value: sealed,
395
+ enumerable: descriptor.enumerable,
396
+ writable: false,
397
+ configurable: false,
398
+ });
399
+ const installed = objectGetOwnPropertyDescriptor(rawPrototype, name);
400
+ if (!installed || installed.value !== sealed || installed.writable !== false ||
401
+ installed.configurable !== false || installed.enumerable !== descriptor.enumerable) {
402
+ // This is unreachable for the preflighted ordinary-object commit, but a
403
+ // false attestation is never acceptable if a runtime violates it.
404
+ refuse("ENTRY_POINT_NOT_DECLARED");
405
+ }
406
+ arities[name] = arity;
407
+ installedCodes[name] = code;
408
+ }
409
+ return objectFreeze({
410
+ codes: objectFreeze(installedCodes),
411
+ // The declared parameter count of each method BEFORE sealing. Read by
412
+ // `entry-position-sweep.test.mjs`, which previously asserted arity against
413
+ // the installed wrapper — a value this function fixes at 1, so the
414
+ // assertion could never fail and the drift it guarded was invisible.
415
+ declaredArity: objectFreeze(arities),
416
+ });
417
+ }