Skip to content

[AAASM-1561] ✨ (sdk): Add enforcementMode parameter to initAssembly#48

Merged
Chisanan232 merged 3 commits into
masterfrom
v0.0.1/AAASM-1561/feat/enforcementMode
May 23, 2026
Merged

[AAASM-1561] ✨ (sdk): Add enforcementMode parameter to initAssembly#48
Chisanan232 merged 3 commits into
masterfrom
v0.0.1/AAASM-1561/feat/enforcementMode

Conversation

@Chisanan232
Copy link
Copy Markdown
Contributor

@Chisanan232 Chisanan232 commented May 23, 2026

Description

Per-agent governance posture override for the Node.js SDK. Mirrors the design that landed in AAASM-1560 (python-sdk #59):

import { initAssembly } from '@agent-assembly/sdk';

const ctx = await initAssembly({
  gatewayUrl: 'http://localhost:8080',
  apiKey: '...',
  agentId: 'experimental-agent',
  enforcementMode: 'observe',   // NEW
});
// Agent runs under sandbox / dry-run posture; gateway records would-be
// violations as shadow audit events surfaced by `aa audit list --dry-run-only`.

Shipped

Item Location
EnforcementMode = 'enforce' | 'observe' | 'disabled' union type src/types/enforcement-mode.ts
ENFORCEMENT_MODES readonly-array constant for runtime validation Same file
AssemblyConfig.enforcementMode?: EnforcementMode (optional) src/types/assembly-config.ts
AssemblyContext.enforcementMode?: EnforcementMode (echo) src/types/assembly-context.ts
initAssembly rejects unknown strings with RangeError src/core/init-assembly.ts
buildRegistrationEvent emits enforcement_mode (snake_case) when set Same file
AssemblyContext echoes the value at return Same file
Public re-exports from package root src/types/index.ts + src/index.ts

Type of Change

  • ✨ New feature

Breaking Changes

  • No

AssemblyConfig.enforcementMode is optional. When omitted, buildRegistrationEvent leaves the field off the wire — a pre-feature SDK call produces a pre-feature event_type: register body shape, and the gateway then applies its server-side default (live enforce). Semantically identical to before. Explicit 'enforce' / 'observe' / 'disabled' from the caller still serialize.

Related Issues

  • Related Jira ticket: AAASM-1561 (Story AAASM-1553)
  • Depends on (already merged): AAASM-1555 (proto field), AAASM-1557 (gateway registry storage), AAASM-1564 (gateway service-layer wiring)
  • Sibling: python-sdk #59 / AAASM-1560 — merged, sets the design precedent (omit-when-unset → preserves wire shape)

What's in here

3 commits:

  1. ✨ Wire enforcementMode end-to-end in initAssembly (new type alias + AssemblyConfig + AssemblyContext + validation + buildRegistrationEvent + context echo)
  2. ✅ Eight new vitest cases — 3 parametrized + 5 standalone (each-mode-accepted / default-undefined / invalid-rejected / unrelated-field-isolation / body-emits-when-set / body-omits-when-unset)
  3. 🔧 Re-export EnforcementMode + ENFORCEMENT_MODES from package root so callers don't need the /types subpath

Testing

pnpm typecheck   # clean
pnpm lint        # clean
pnpm test        # 179 passed, 2 skipped (unchanged from master baseline)

AC checklist

  • initAssembly({ enforcementMode: 'observe' }) passes enforcement_mode: OBSERVE in RegisterAgent (wire shape verified by emits enforcement_mode on the registration body when set)
  • enforcementMode defaults to 'enforce' semantically (via gateway server-side default when unset); existing tests pass without changes
  • TypeScript type system rejects invalid string values at compile time (union type narrows; runtime fallback covers JS / JSON-config callers via RangeError)
  • pnpm typecheck clean
  • Unit tests added (8 covering parser-level, runtime validation, wire-shape)

Checklist

  • Code follows project style guidelines (pnpm lint, pnpm format — only my changed files committed; pre-existing format drift in 56 unrelated files was deliberately not staged)
  • Self-review of the diff completed
  • Documentation updated if behaviour changed (JSDoc on new symbols)
  • All CI checks passing
  • Commits are small and follow the Gitmoji convention

Operator surface for the per-agent observe-mode override the gateway
landed in AAASM-1557 + AAASM-1564, mirroring AAASM-1560 (Python SDK).

* New EnforcementMode = 'enforce' | 'observe' | 'disabled' union type
  in src/types/enforcement-mode.ts, exported from the public types index.
* New ENFORCEMENT_MODES readonly-array constant for runtime validation
  from non-TS callers (plain JS, JSON config, dynamic input).
* AssemblyConfig gains optional enforcementMode?: EnforcementMode.
  Default (undefined) omits the field from the registration body so a
  pre-feature SDK call produces a pre-feature wire shape; the gateway
  applies its server-side default of live enforce.
* initAssembly validates an unknown string with a RangeError so a typo
  fails fast instead of silently registering under live enforcement.
* buildRegistrationEvent emits enforcement_mode (snake_case wire token)
  when set. The gateway's REST -> gRPC bridge maps it onto
  RegisterRequest.enforcement_mode (proto enum) per AAASM-1555.
* AssemblyContext echoes the value for parity with the existing
  parentAgentId / teamId / delegationReason fields.

Refs: AAASM-1561
Eight new vitest cases (3 parametrized + 5 standalone) covering AAASM-1561:

* Each of enforce / observe / disabled lands on AssemblyContext
* Default (undefined) is preserved on the context
* Unknown string raises RangeError with a clear message
* Setting an unrelated field doesn't leak enforcementMode onto context
* Registration body carries enforcement_mode (snake_case) when set
* Registration body omits enforcement_mode when caller didn't set it

The wire-shape tests reuse the mock-binding pattern from
tests/topology-registration.test.ts so the assertion is on the actual
event passed to the native sendEvent call.

Refs: AAASM-1561
Lets callers import the type/value directly from '@agent-assembly/sdk':

    import { initAssembly, type EnforcementMode } from '@agent-assembly/sdk';

instead of the longer '@agent-assembly/sdk/types' subpath. Matches the
re-export pattern the package already uses for AssemblyConfig /
AssemblyContext / AssemblyMode.

Refs: AAASM-1561
@codecov
Copy link
Copy Markdown

codecov Bot commented May 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@sonarqubecloud
Copy link
Copy Markdown

@Chisanan232
Copy link
Copy Markdown
Contributor Author

Review by Claude Code

CI status

All 26 checks green on the first pass — no flakes, no reruns.

Category Status
Build (ESM + CJS) ✅ pass
Typecheck (tsc --noEmit) ✅ pass
Lint (eslint) + Format check (prettier) ✅ pass
Unit + integration tests (vitest) ✅ pass
Coverage / Codecov ✅ pass
Native binding build (napi-rs) ✅ pass
Package size / packaging tests / npm-pack content ✅ pass
Conformance ✅ pass

Scope review vs AAASM-1561

AC Status Where
initAssembly({ enforcementMode: 'observe' }) passes enforcement_mode: OBSERVE in RegisterAgent emits enforcement_mode on the registration body when set test — asserts the native sendEvent is called with { event_type: 'register', enforcement_mode: 'observe' } (snake_case wire token, mapped to proto enum by gateway REST → gRPC bridge per AAASM-1555)
enforcementMode defaults to 'enforce' (semantically); existing tests pass without changes Achieved via "omit-when-unset" — defaults to undefined when omitted so the registration body keeps its pre-feature shape + the gateway's server-side default of live enforce apply. The pre-existing 173-test suite passes unmodified
TypeScript type system rejects invalid string values at compile time EnforcementMode = 'enforce' | 'observe' | 'disabled' union narrows; runtime fallback catches non-TS callers (plain JS, JSON config, dynamic input) via RangeError
pnpm typecheck clean CI Typecheck job green
Unit tests added 8 new vitest cases (3 parametrized over each mode + 5 standalone for default / invalid / context-isolation / wire-emit / wire-omit)

Design choices worth noting

Item Why it matters
Omit-when-unset wire semanticenforcementMode: undefined → field absent from the registration body Same trade-off resolved in AAASM-1560 after CI caught it: defaulting to "enforce" would have broken the pre-feature wire-shape contract ({ event_type: 'register' } bare body). Defaulting to undefined preserves wire shape; the gateway applies its server-side enforce default — semantically identical
EnforcementMode union + ENFORCEMENT_MODES readonly-array constant Union type catches TypeScript callers at compile time; runtime constant catches JS / JSON-config / dynamic-input callers with a clear RangeError
AssemblyConfig.enforcementMode? mirrored on AssemblyContext.enforcementMode? (echo) Matches the existing parentAgentId / teamId / delegationReason / spawnedByTool pattern — the caller can introspect what was sent
Re-exported from package root (@agent-assembly/sdk) Callers don't need the /types subpath — import { initAssembly, type EnforcementMode } from '@agent-assembly/sdk' works straightforwardly
Wire-shape tests reuse the mock-binding pattern from tests/topology-registration.test.ts Assertion is on the actual event passed to native sendEvent, not a derived state — same level of confidence as the topology tests

Bisectability

3 commits, each builds and tests green at its tip:

  1. ✨ End-to-end wiring (type alias + config field + context echo + validation + buildRegistrationEvent)
  2. ✅ 8 vitest cases
  3. 🔧 Re-export from package root

Verdict

Ready to approve and merge.

After merge, three SDKs will be at parity with aa run --observe on the operator-CLI side and aa audit list --dry-run-only on the consumer side:

import { initAssembly, type EnforcementMode } from '@agent-assembly/sdk';

const mode: EnforcementMode = process.env.AA_ENFORCEMENT_MODE === 'observe' ? 'observe' : 'enforce';
const ctx = await initAssembly({
  gatewayUrl: 'http://localhost:8080',
  apiKey: '...',
  agentId: 'experimental-agent',
  enforcementMode: mode,
});

The last SDK pickup — AAASM-1562 (Go SDK) — is fully independent of any open PR. After that, only AAASM-1563 (dashboard) and AAASM-1565 (docs) remain.

@Chisanan232 Chisanan232 merged commit a26053e into master May 23, 2026
26 checks passed
@Chisanan232 Chisanan232 deleted the v0.0.1/AAASM-1561/feat/enforcementMode branch May 23, 2026 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant