Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLIPublic
Assigneeszack

Relationships

#3551 typed-secrets 1.2: pin the Secret Proxy, refusal set and container rule on cel-js 7.6.1

Opened by zack · 10/16/2026· Shipped 10/16/2026

Outcome

The Phase 1.2 tests in design/enablers/typed-secrets.md exist and pass against @marcbachmann/cel-js 7.6.1. They show that the design's Secret value and its refusals work on the pinned dependency. If one does not work, the test records the fallback the doc names. The answer tells Phases 3 and 4 which shape to build: the Proxy-based Secret (Section 3.1), or the plain class with private fields, with the container rule extended to comprehension macros.

Why now

Phase 1.1 (swamp-club#3510) pinned what cel-js does today (Section 4.2). Phase 1.2 pins what the design needs cel-js to do with a Secret registered. Phases 3 and 4 depend on its exit, and Phase 2 does not, so it can run beside Phase 2.

This issue replaces swamp-club#3517. That issue described the old Phase 1.2: an async CelVaultNamespace receiver, loaded and unloaded secrets, and equality on loaded secrets. The cut plan (swamp-club#3545) dropped all of those.

Scope

Add pin tests beside the Phase 1.1 suite in src/infrastructure/cel/cel_js_pins/. Build a test fixture Secret that has the shape Section 3.1 specifies:

  • a Proxy over a frozen, empty object, with its state in a module-private WeakMap;
  • ownKeys and getOwnPropertyDescriptor traps that throw;
  • a get trap that answers constructor, returns throwing functions for toString, toJSON, valueOf and Symbol.toPrimitive, and returns undefined for every other key;
  • every throwing trap and function records a fault in an AsyncLocalStorage-scoped fault context before it throws.

This is a test fixture, not production code. Nothing outside the pin directory imports it.

Each test pins one claim from the Phase 1.2 list:

  1. cel-js recognizes the Proxy-based Secret by its constructor. Building a macro range over it fails, and that failure is not absorbed.
  2. A custom function sees, at its start, every fault recorded earlier in the same evaluation, across asynchronous evaluation. The effect gate in Section 4.5 depends on this.
  3. {S: 1} and m[S] record a fault through a Proxy trap, so a map-key refusal is visible under || even though a map literal calls no overload.
  4. An async custom function can return a Secret, and awaiting it reads then without a fault.
  5. The refusing overload set covers every built-in that accepts a string or a dyn value:
    • It includes == between two secrets. There is one refusing Secret == T overload for each type cel-js accepts in a signature, plus a Secret == null overload that answers false in both operand orders.
    • Adding Secret == Secret beside Secret == dyn does not remove the overlap. Record that the enumerated set is the only shape that works.
  6. A recorded fault survives absorption by ||, &&, exists and all, across asynchronous evaluation.
  7. Synchronous entry points still return synchronous results with the Secret registrations in place. All refusing handlers are synchronous, and functions use { async: false }.
  8. vault.get, with the static return type Secret, gives check-time errors in the validation environment for everything outside the allowed set in Section 4.1. The allowed set is passing along, concatenation, comparison with null, and has() or .?.
  9. The container rule in Section 4.3, item 5, refuses every case the fact-2 pins answer by identity (cel_js_equality_fallback_pin_test.ts). This needs two things:
    • cel-js reads a record field, a list element and a map value through ordinary property access, so a getter records the secret on selection and on container traversal.
    • Map key membership, and selecting a different field, do not record it.

Also add the refusal-coverage fitness test that Section 4.3 describes. It lists the internal environment's built-ins, plus swamp's own registrations in the internal environment and the query view (the data, file and model namespaces). It fails when a string, dyn or list<dyn> parameter has neither a Secret refusal nor a named delivery (model.method inputs). Decide, and write into the test, whether the built-in list comes from cel-js or from a maintained list pinned against 7.6.1. The == type list is maintained either way, because cel-js does not export its type table.

Follow the Phase 1.1 conventions:

  • Name each test with pin(...) from cel_js_pin_test_helpers.ts, and quote the doc claim it pins in the file header.
  • Run in-process: no subprocesses, sockets or env mutation.
  • Add license headers with deno run license-headers.

Exit

Every claim above is observed by a passing test. If a claim fails:

  • Record that in a pin.
  • Switch the doc to the Section 3.1 fallback (a plain class with private fields), with the container rule covering comprehension macros.
  • Update the Phase 3 and Phase 4 scope in the doc to match.

Out of scope

  • Production code. No Secret type, adapter or registration enters src/infrastructure/cel/cel_evaluator.ts or any production path. Phase 3 builds those.
  • Phase 2 (sentinels hold references), which does not depend on this.
  • Phases 3 to 5, which get their own issues after this exit.
02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 9 MOREREVIEW+ 16 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/16/2026, 6:55:38 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
zack assigned zack10/16/2026, 5:16:01 PM

Sign in to post a ripple.