feat: 1.0.0-rc.0 surface reconciliation (Branch A)#1
Merged
Conversation
Group 10 of MECHANICAL_RESOLUTION_SUMMARY.md. Mechanical sweep, no public-API change. - Set publishConfig.access=public on all 24 packages. - Set publishConfig.provenance=true on all 24 packages. - Set sideEffects=false where unset; preserve explicit values. - Remove duplicate dependency entries that are also peerDependencies (resolves audit F-19/F-22 for adapter-pagerduty, adapter-vercel-ai; also corrects an undocumented runtime/@loop-engine/events duplication). - Standardize engines.node floor to >=18.17 across all packages. Refs: oss-repackaging--02a-mechanical-pass-a.md
Group 1 of MECHANICAL_RESOLUTION_SUMMARY.md. Each item is removal of a surface that the catalog flagged as dead (R-183, R-184, R-163, plus audit F-08/F-25 changeset cleanup). - core: remove unused LOOP_ENGINE_CORE_VERSION constant (R-183). - core: delete the stub src/types.ts (R-184; nothing imports it). - sdk: remove createLoopSystemRuntime alias (R-163; createLoopSystem is the single canonical name). - changeset: drop ignore entries for @loop-engine/inspector and @loop-engine/playground (apps, never published; the dsl ignore stays). Refs: oss-repackaging--02a-mechanical-pass-a.md
Group 2 of MECHANICAL_RESOLUTION_SUMMARY.md. Catalog-locked behavior fixes. - adapters/http: httpEventBus.emit now awaits fetch and surfaces non-2xx responses as thrown errors (R-077). Previously the promise was created and discarded, hiding network/transport failures. - guards: GuardResult.message is now optional (R-093) — evaluators may legitimately pass without a human-readable message. The pipeline defaults a missing message to "" at the runtime/event boundary so downstream event payloads keep a string. Refs: oss-repackaging--02a-mechanical-pass-a.md
…exports Group 3 of MECHANICAL_RESOLUTION_SUMMARY.md. Pure additions — no behavior or signature change to anything that already shipped. - events: export LOOP_EVENT_TYPES constant + LoopEventType union (R-103). - guards: export createGuardRegistry() factory and defaultRegistry pre-loaded with built-ins (R-091). - runtime: add optional EventBus.subscribe?() so SDK consumers can observe events without naming a concrete bus implementation (R-071). Optional rather than required to keep existing implementers (httpEventBus, kafkaEventBus, openclawEventBus) source-compatible — see PASS_A_CALIBRATION_NOTES for why this differs from the catalog tag of "strict additive." - loop-definition: add parseLoopJson() and serializeLoopJson() wrappers so JSON loop definitions have a first-class read/write path (R-113 subset). Refs: oss-repackaging--02a-mechanical-pass-a.md
Per resolution log D-07 (Engine class & method naming, Direction A): rename the runtime engine class and factory, and the engine's lifecycle methods, with no aliases and no dual names anywhere in the runtime or its consumers. Renames in this commit: - runtime class: LoopSystem -> LoopEngine - runtime factory: createLoopSystem -> createLoopEngine - runtime options: LoopSystemOptions -> LoopEngineOptions - engine method: startLoop -> start - engine method: getLoop -> getState What stays unchanged: - SDK aggregate factory createLoopSystem keeps its name; per D-07 it is the intentional product name for the auto-wired aggregate and not an alias to the runtime. The SDK now imports createLoopEngine internally and re-exports the LoopEngine type. - LoopStorageAdapter.getLoop stays per D-11 (separate row, handled in SR-002). Internal calls to options.storage.getLoop are preserved verbatim. Updated callers and docs in this repo: - runtime engine, interfaces, and tests - adapter-vercel-ai bridge and types - sdk barrel, sdk tests, and registry-integration tests - apps/playground page that constructs the engine - root, runtime, and sdk READMEs - adapter-openclaw SKILL.md and loop-engine-governance SKILL.md - five loop-engine-governance example scripts Verification: - pnpm --filter @loop-engine/runtime build (refreshes dist for downstream workspace resolution) - pnpm -r typecheck: green across all 26 packages - pnpm -r test: green Cross-repo pair: loopengine.dev co-commit applies the matching docs renames under the same Surface-Reconciliation-Id below. Surface-Reconciliation-Id: SR-001
Land the rolling changeset entry for the 1.0.0-rc.0 coordinated release. The file accumulates one section per Surface-Reconciliation-Id (SR-NNN) as Phase A.1 lands. SR-001 (D-07 engine class & method naming) is the first section. All bumps are major per D-07's no-alias policy: any consumer that imported the pre-rename names from @loop-engine/runtime needs a code change. SDK consumers using createLoopSystem from @loop-engine/sdk are unaffected (intentional product-name preservation, not an alias). Subsequent SRs in Phase A.1 will append further sections under the same file. Per --03 Phase A.7, this file's existence is a verification gate for the coordinated-release tooling. Refs: SR-001 (loop-engine 6241eac, loopengine.dev a1667ca).
…D-11) Single Class 2 change: collapse + rename of the storage interface, its implementers, and the SDK option key. Six methods become five via saveInstance upsert semantics. Interface - LoopStorageAdapter -> LoopStore (runtime/src/interfaces.ts) - LoopEngineOptions.storage -> LoopEngineOptions.store Methods (6 -> 5; collapse + rename) - getLoop -> getInstance - createLoop + updateLoop -> saveInstance (upsert) - appendTransition -> saveTransitionRecord - getTransitions -> getTransitionHistory - listOpenLoops -> listOpenInstances Implementers (active, in-repo) - adapter-memory: MemoryLoopStorageAdapter -> MemoryStore; createMemoryLoopStorageAdapter -> memoryStore (factory returns the concrete class per spec). Drops dead lastUpdated field. - adapters/postgres: postgresStorageAdapter consolidated into postgresStore; saveInstance implemented as INSERT ... ON CONFLICT (aggregate_id) DO UPDATE SET ... (per the operator's pre-check classification: real/functional adapter, update in SR-002). - runtime engine call sites and runtime test mock updated. - apps/playground InMemoryLoopStore mock updated. SDK - CreateLoopSystemOptions.storage -> store - createLoopSystem return shape: storage -> store - Re-exports updated (memoryStore, MemoryStore, LoopStore). Docs (in-repo READMEs co-committed; loopengine.dev co-committed in the paired commit per Surface-Reconciliation-Id: SR-002) No partial aliasing. No dual method names. No type alias to old name. Per D-11, every consumer migrates to the new surface. Verification (Phase A.7 partial; full report in PASS_B_EXECUTION_LOG.md) - pnpm --filter @loop-engine/runtime build (per C-07): green - pnpm -r typecheck: 26 packages, all Done - pnpm -r test: 137 tests, all passed - pnpm typecheck:examples: green - npm pack --dry-run: runtime 13.1KB, sdk 14.1KB, adapter-memory 6.5KB, adapter-postgres 8.6KB (all well under ceilings) - d.ts surface diff: runtime exports LoopStore (no LoopStorageAdapter); adapter-memory exports MemoryStore + memoryStore (no MemoryLoopStorageAdapter); postgres exports postgresStore (no postgresStorageAdapter) - bd-forge-main split scan (per C-08): producer-side 6 F-01 stub hits (unchanged from SR-001 baseline; routed to Phase E --13-bd-forge-main-cleanup.md); consumer-side hits in apps/* are legitimate consumers, packages/* are deferred Phase E targets - .changeset/1.0.0-rc.0.md appended with SR-002 entry; package set expanded to include @loop-engine/adapter-memory and @loop-engine/adapter-postgres at major bumps F-PB-03 (D-11 prompt structure mismatches actual source state) logged to PASS_B_FINDINGS.md per the operator's directive. Phase A.1 classifies D-11 as Class 1 and Phase A.2 treats listOpenInstances as an addition; both incorrect. Actual work is Class 2 (collapse + rename). Targeted prompt-vs-source audit scheduled (non-blocking). Surface-Reconciliation-Id: SR-002
Narrow Class 1 rename per Phase A.1 of the API surface execution
plan. Renames the interface `LLMAdapter` exported from
`@loop-engine/core` to `ToolAdapter`, and renames the source file
`packages/core/src/llmAdapter.ts` to `toolAdapter.ts` (git mv;
history preserved). The barrel `packages/core/src/index.ts` is
updated to re-export the new file.
The single in-tree implementer is `PerplexityAdapter` in
`@loop-engine/adapter-perplexity` (the lone consumer of this
interface today). Its import and `implements` clause are updated
in the same commit. Per Class 1 inter-package procedure, the
upstream `@loop-engine/core` package was rebuilt before workspace
typecheck (per C-07) so consumers resolve via the refreshed
`dist/index.d.ts`.
The other four AI provider adapters (Anthropic / OpenAI / Gemini /
Grok / Vercel-AI) are explicitly out of scope for this row. They
do not implement `LLMAdapter` today; each carries a bespoke
`*ActorAdapter` shape, and they re-home onto `ActorAdapter` (a
separate, new interface introduced in Phase A.2) in Phase A.3 —
not onto `ToolAdapter`. See D-13 in
`API_SURFACE_DECISIONS_RESOLVED.md`.
The `guardEvidence` helper function colocated in the renamed file
is unaffected by this commit. Its planned consolidation into
`@loop-engine/core` (deduping the `packages/sdk/src/lib/`
copy) is a separate Phase A.3 row per F-PB-14 / MECHANICAL 8.16.
Class 4B paired docs edits in `loop-engine` prose
(`README.md`, `docs/getting-started.md`,
`docs/integrations-perplexity.md`,
`packages/adapter-perplexity/{README,BOUNDARY-MANAGEMENT}.md`,
`.cursor/rules/adapter-perplexity.mdc`) update interface
references to `ToolAdapter`. The `CHANGELOG.md` Unreleased
section (which becomes the 1.0.0-rc.0 entry) is updated; the
0.1.5 historical entry is preserved untouched.
Verification: `pnpm --filter @loop-engine/core build` (per C-07)
green; `pnpm -r typecheck` green across 26 packages;
`pnpm -r test` green; `pnpm typecheck:examples` green;
`pnpm --filter @loop-engine/adapter-perplexity test` green
(9/9 passed). `dist/index.d.ts` for `@loop-engine/core` exports
`type ToolAdapter`; `LLMAdapter` is absent from the surface.
A producer-side F-01 violation in `bd-forge-main` (the
`loopengine-core` stub) caused an initial typecheck failure when
adapter-perplexity's `node_modules/@loop-engine/core` symlink
misrouted to the stub instead of the in-repo workspace package.
Symlink repaired locally; finding logged as F-PB-15 with
calibration recommendation C-10 in
`bd-forge-main/PASS_B_FINDINGS.md` and
`PASS_B_CALIBRATION_NOTES.md`. Stub deletion remains routed to
Phase E `--13-bd-forge-main-cleanup.md`.
Surface-Reconciliation-Id: SR-003
Adds @loop-engine/core and @loop-engine/adapter-perplexity to the 1.0.0-rc.0 package set (both major). Documents the LLMAdapter → ToolAdapter rename with migration diff and explicit out-of-scope note explaining that the four other AI provider adapters re-home onto ActorAdapter (Phase A.3), not onto ToolAdapter.
…ionRecord (MECHANICAL 8.5)
Renames per MECHANICAL 8.5 / D-07's "no dual names anywhere" clause:
- RuntimeLoopInstance → LoopInstance
- RuntimeTransitionRecord → TransitionRecord
Both interfaces relocated from @loop-engine/runtime to
@loop-engine/core (new file packages/core/src/loopInstance.ts) so
they're available on the core public surface, satisfying the prompt's
"re-export from @loop-engine/core" directive. Field-type imports
(LoopId, AggregateId, StateId, ActorRef, etc.) all already lived in
core, so no dependency cycle introduced.
Referrers updated to import from @loop-engine/core:
- packages/runtime/src/{interfaces,engine}.ts + engine.test.ts
- packages/observability/src/{timeline,replay,metrics}.ts + test
- packages/adapter-memory/src/index.ts
- packages/adapters/postgres/src/index.ts
- packages/sdk/src/index.ts (drops explicit Runtime* re-export;
new names propagate via the existing `export * from "@loop-engine/core"`)
Class 1 inter-package rename per the reconciled --03 prompt.
Procedure executed:
rename → pnpm --filter @loop-engine/core build (C-07)
→ pnpm --filter @loop-engine/runtime build (C-07)
→ workspace symlink integrity check (C-10) — clean
→ pnpm -r typecheck — green (26 packages)
→ pnpm -r test — green (137+ tests)
→ pnpm typecheck:examples — green
→ commit
No loopengine.dev co-commit: pre-flight scan returned zero MDX
references to the pre-rename names (already at post-rename state).
Attribution per F-PB-04: this work is sanctioned by MECHANICAL 8.5
and implied by D-07's "no dual names anywhere" clause plus the spec
draft's use of the post-rename names; not enumerated explicitly in
D-07's resolution log text.
Surface-Reconciliation-Id: SR-004
Adds SR-004 section to .changeset/1.0.0-rc.0.md documenting the RuntimeLoopInstance → LoopInstance, RuntimeTransitionRecord → TransitionRecord rename and the relocation from @loop-engine/runtime to @loop-engine/core. Adds @loop-engine/observability to the major-bump list (its public exports reference the renamed types via LoopMetrics computeMetrics signature). Surface-Reconciliation-Id: SR-004
…ublic surface (D-09) Adds listOpen(loopId: LoopId): Promise<LoopInstance[]> on the LoopEngine class, delegating to the underlying LoopStore.listOpenInstances method (added in SR-002 per D-11). Verifies the existing cancelLoop and failLoop methods are public (both async, not marked private — already true in source; no shape change). registerSideEffectHandler explicitly omitted per D-09 deferral to 1.1.0. Class 2 row, but the implementer count is 1 (LoopEngine is a concrete class, not an abstract interface). The new method is a pure delegation to an already-existing LoopStore method that every conforming store must implement (verified across MemoryStore in tests and the existing postgresStore + adapter-memory). New test (12) covers the listOpen contract: returns only active instances for the given loopId, excludes completed instances. Existing tests for cancelLoop/failLoop remain unchanged (verification was confirmation, not modification). Per Class 1 procedure: rebuild @loop-engine/runtime (C-07), symlink integrity check (C-10) caught a stale adapter-perplexity/node_modules/@loop-engine/core relink to the bd-forge-main F-01 stub (the same misroute SR-003 hand-repaired — pnpm re-linked it during an intervening operation). Repaired in-place; pnpm -r typecheck and pnpm -r test green. Per Phase A.7: pnpm -r build (C-11) green; d.ts surface confirms listOpen on the dist with correct signature; tarball ceilings clean (runtime 12.9 KB ≤ 250 KB; core 13.4 KB ≤ 500 KB); no Runtime* leaks in SDK dist (C-11 SDK rebuild from SR-004 still holds); bd-forge-main producer-side scan baseline unchanged at 6 stubs; consumer-side hits all in apps/ (legitimate). Surface-Reconciliation-Id: SR-005
…lLoop verification) Appends SR-005 section to 1.0.0-rc.0.md documenting the new LoopEngine.listOpen public method (D-09) plus the cancelLoop/ failLoop public-surface confirmation. Notes registerSideEffectHandler as intentionally out of scope per D-09's 1.1.0 deferral. The package set in the frontmatter is unchanged from SR-004; SR-005 only modifies @loop-engine/runtime, which already carries a major bump for SR-001 (LoopSystem rename) and SR-004 (Runtime* prefix removal). No additional package needs adding. Surface-Reconciliation-Id: SR-005
…sion + AIAgentActor to core (D-13; PB-EX-01 Option A + PB-EX-04 Option A)
Defines the new ActorAdapter interface in @loop-engine/core,
alongside relocated AI archetype types. ActorAdapter is genuinely
net-new (zero implementers in source; five expected implementers
re-home in Phase A.3). The four supporting types
(AIAgentSubmission, LoopActorPromptContext, LoopActorPromptSignal,
AIAgentActor) already existed in @loop-engine/actors and are
relocated to @loop-engine/core in this same commit per the D-13
first + second extensions in API_SURFACE_DECISIONS_RESOLVED.md
(originating findings: PB-EX-01 + PB-EX-04 in
PASS_B_EXCEPTIONS.md).
Why relocate. Placing ActorAdapter in core as D-13 specifies
requires that core be closed under its own type graph: every type
ActorAdapter or its referenced types depend on must already live
in core or be relocated alongside it. AIAgentSubmission /
LoopActorPromptContext / LoopActorPromptSignal are referenced by
ActorAdapter.createSubmission's signature; AIAgentActor is
referenced by AIAgentSubmission.actor. core has zero workspace
dependencies and core/tsconfig.json has no paths entry pointing
at @loop-engine/actors, so an import-type from actors into core
cannot resolve under the project's bundler module resolution.
Relocating the four types together breaks the would-be
core → actors → core type-level cycle (both at the contract
surface per PB-EX-01 and at the residual
AIAgentSubmission.actor: AIAgentActor reference chain per
PB-EX-04). Pattern mirrors SR-004's Runtime* relocation from
runtime to core.
Pre-commit structural check (per C-12 calibration). AIAgentActor's
transitive type graph audited at SR-006 execution start: extends
ActorRef (already in core); all field types are primitives. No
recursive cycle to break; PB-EX-05 hazard cleared.
Source surface changes.
- Net-new file: packages/core/src/actorAdapter.ts (ActorAdapter
interface + the four relocated types). Re-exported through
core's barrel via packages/core/src/index.ts.
- packages/actors/src/types.ts: removes the four relocated type
declarations; preserves the Actor union by importing
AIAgentActor from @loop-engine/core; Zod schema
AIAgentActorSchema (a value, not part of the type-level cycle)
stays in actors, consistent with the rest of the actor
schemas.
- packages/actors/src/ai-evidence.ts: imports AIAgentSubmission
from @loop-engine/core instead of "./types".
- Five AI adapter packages updated to import AIAgentActor /
AIAgentSubmission / LoopActorPromptContext from
@loop-engine/core; ActorDecisionError, AIActorDecision,
buildAIActorEvidence remain imported from @loop-engine/actors:
- adapter-anthropic/src/index.ts
- adapter-openai/src/index.ts
- adapter-gemini/src/{adapter,types}.ts +
__tests__/gemini.test.ts
- adapter-grok/src/{adapter,types}.ts +
__tests__/grok.test.ts
Note: adapter-vercel-ai doesn't currently reference any of the
four relocated types directly; its re-homing onto ActorAdapter
lands in Phase A.3.
- examples/ai-actors/shared/actors.ts: imports AIAgentActor from
@loop-engine/core. (The buildActorEvidence vs
buildAIActorEvidence mismatch in this example is pre-existing
and out of scope for SR-006; routes to Branch C work.)
Verification per Class 2 + C-07/C-10/C-11/C-12 procedure.
- C-12 pre-commit transitive-type-graph audit: clean.
- C-07 upstream rebuild: pnpm --filter @loop-engine/core build
+ pnpm --filter @loop-engine/actors build (both rebuilds
required because relocation flows in both directions —
symbols leave actors, enter core).
- C-10 symlink integrity: caught one stale symlink at
adapter-perplexity/node_modules/@loop-engine/core pointing
at bd-forge-main/packages/loopengine-core (the recurring
containment-procedure case); repaired in-place to
../../../core.
- pnpm -r typecheck: clean across 26 packages.
- pnpm -r test: 144 tests passing across 19 test-bearing
packages (no test changes needed — the type imports updated
in the adapter test files resolved without semantic change).
- pnpm typecheck:examples: clean.
- C-11 workspace rebuild: pnpm -r build clean. Required a
one-time clean of packages/core/dist + .turbo to clear
stale tsup output that initially missed the new file's
exports (cache invalidation didn't pick up the new file
on first rebuild — interesting calibration nuance worth
tracking but not yet a finding; symptom is specific to
first-time-add-of-file scenarios).
- d.ts surface check: core dist/index.d.ts exports all 5 new
symbols (ActorAdapter, AIAgentActor, AIAgentSubmission,
LoopActorPromptContext, LoopActorPromptSignal); actors
dist/index.d.ts no longer lists them in its export
statement (only imports them internally for the Actor union
and ai-evidence return type); SDK dist propagates them via
export * from "@loop-engine/core" (re-export syntax, not
inlined symbol names — propagation accessibility verified
via the workspace typecheck pass).
- npm pack --dry-run: core 14.5 kB, actors 9.0 kB, all four
AI adapters under 10 kB (well under ceilings).
- C-08 producer-side scan: F-01 baseline unchanged (6 known
stubs in bd-forge-main).
Implementer count at this commit: zero by design (ActorAdapter
is a net-new contract; D-13 second extension's package change
to AIAgentActor doesn't have implementers — only consumers
update import paths). Five expected ActorAdapter implementers
re-home onto this contract in Phase A.3.
No docs co-commits at this phase per Phase A.2 convention —
loopengine.dev pages reference these symbols by name only (not
in import statements with @loop-engine/actors), so no Class 4B
churn risk; docs work lands in Branch B Phase B.2 once the
source surface is finalized.
Surface-Reconciliation-Id: SR-006
ActorAdapter archetype introduction + four-type relocation from @loop-engine/actors to @loop-engine/core (AIAgentActor, AIAgentSubmission, LoopActorPromptContext, LoopActorPromptSignal). All affected packages already enumerated in the existing 1.0.0-rc.0 changeset frontmatter at major. Surface-Reconciliation-Id: SR-006
…ActorConstraints + pending_approval (D-08)
Three structurally related pieces of the D-08 → A ("the hook
that proves governance is real, not the governance system")
resolution, landed as a single coherent commit per Class 2 batch
semantics.
Piece 1: rename isAuthorized → canActorExecuteTransition.
Function renamed at packages/actors/src/authorization.ts:11
(the sole definition site). Call site updated at
packages/runtime/src/engine.ts:178 (previously :174; line shifted
+4 by the new pending_approval branch). Barrel in
packages/actors/src/index.ts re-exports via `export * from
./authorization`, so symbol substitutions propagate to the SDK
via `export * from @loop-engine/actors`. Test suite updated at
packages/actors/src/__tests__/actors.test.ts: rename import,
rename three test names + bodies.
Return shape widens from { authorized, reason? } to { authorized,
requiresApproval, reason? }. The new requiresApproval field is
mandatory, not optional — callers doing `if (!authorization.authorized)`
continue to work; callers reading `authorization.requiresApproval`
get a well-defined boolean. Pre-existing ActorAuthorizationResult
interface renamed accordingly.
Piece 2: add AIActorConstraints type.
New interface in packages/actors/src/authorization.ts exporting
only `requiresHumanApprovalFor?: TransitionId[]`. Scope is
explicitly narrow per D-08 → A: no maxConsecutiveAITransitions,
no canExecuteTransitions, no policy DSL, no constraint engine.
Co-located with canActorExecuteTransition rather than moved to
@loop-engine/core because core doesn't reference it from any
contract surface (the PB-EX-01/04 precedent doesn't apply —
that was types referenced by core-owned ActorAdapter, which is
not the case here). Threaded through canActorExecuteTransition
as an optional third parameter; when the actor is AI and the
transition is in the constrained set, returns
{ authorized: true, requiresApproval: true } so the runtime can
route to pending_approval rather than executing.
Piece 3: add pending_approval to TransitionResult.status.
TransitionResult union at packages/runtime/src/engine.ts:57
widens from "executed" | "guard_failed" | "rejected" to include
"pending_approval". Adds requiresApprovalFrom?: ActorId per the
spec draft canonical entry. TransitionParams gains
constraints?: AIActorConstraints so callers can thread
constraints from the engine.transition() call site, making the
hook reachable from the runtime path (not just from direct
callers of canActorExecuteTransition).
The runtime enforces: after canActorExecuteTransition returns,
if requiresApproval is true, transition() returns a
pending_approval result without executing. Guards are not run;
events are not emitted. Approval-flow resolution is caller work
and is out of scope for 1.0.0-rc.0 (spec draft §4 deferrals
enumerate the advanced approval fields not shipping).
Class 2 verification per the implementer-count gate:
- canActorExecuteTransition implementers: 1 (the sole definition
in actors/authorization.ts). No concrete implementers of the
interface at source level; the function is the contract.
- AIActorConstraints implementers: 0 (additive type, no method
gates).
- pending_approval variant handling: audit surfaced no
exhaustive `switch (result.status)` in source. Consumers
present (vercel-ai/loop-tool-bridge.ts, adapter-openclaw
example, runtime tests) all use `if/===` comparisons; the
union widening is additive and does not break exhaustiveness.
Downstream consumers will pick up the new variant when they
adopt 1.0.0-rc.0; explicit per-status switches can be added
at consumer leisure.
Two new tests validate the constraint hook path:
- canActorExecuteTransition flags requiresApproval=true for
AI actor on constrained transition.
- canActorExecuteTransition leaves non-AI actors unaffected by
AIActorConstraints.
Workspace verification, all clean:
- pnpm --filter @loop-engine/actors build — actors dist
refreshed.
- pnpm -r typecheck — 19 packages, zero errors on first pass.
- pnpm -r test — initial run surfaced 2 SDK test failures
(`TypeError: isAuthorized is not a function`) because
runtime/dist was stale. C-07 rebuild of runtime + SDK cleared
it; re-run clean with 140/140 tests passing (actors 6 → 8,
all other suites unchanged).
- pnpm typecheck:examples — clean.
- pnpm -r build — clean workspace rebuild.
- Phase A.7 d.ts surface: actors dist exports
canActorExecuteTransition, AIActorConstraints,
ActorAuthorizationResult (with requiresApproval field);
runtime dist has constraints? param, pending_approval status,
requiresApprovalFrom?: ActorId on TransitionResult; SDK
barrel carries the actors additions via its existing
`export * from @loop-engine/actors`. No unexpected symbols
added or removed.
- Tarball sizes: core 14.5 KB, actors 9.2 KB, runtime 13.1 KB,
sdk 14.2 KB — all far under ceilings.
- C-08 producer-side scan: 6 stubs (baseline unchanged).
- C-10 extended scan (pattern + readlink -f, first SR executing
against the extended scan): zero stale symlinks at SR-start,
no repair needed during execution.
C-NN observations:
- C-07 caught a downstream consumer at test time rather than
typecheck time — the stale runtime/dist issue manifested as a
runtime error in SDK tests (`isAuthorized is not a function`
with source-map-resolved line number pointing at
canActorExecuteTransition-occupied line). Typecheck was clean
because source-level type resolution saw the renamed function.
The Class 1 procedure's rebuild-before-typecheck applied
here: after rebuilding runtime + SDK, tests cleared. Not a
new calibration finding; C-07 already covers "rebuild the
upstream whose dist downstream consumers read." SDK reads
runtime/dist via its package `exports` map; runtime was the
upstream to rebuild, not just actors.
- Observation A (SR-006's tsup cache issue) did not reproduce
on AIActorConstraints's new-export addition. The type
appeared in actors/dist/index.d.ts on first rebuild. Single
instance of the tsup cache miss during SR-006 remains the
only data point; not sufficient to escalate to C-13.
Surface-Reconciliation-Id: SR-007
Appends SR-007 section after SR-006. Covers the three D-08 → A pieces landed in ea34067: - isAuthorized → canActorExecuteTransition rename with widened signature (constraints? param) and return shape (requiresApproval field). - AIActorConstraints interface with single requiresHumanApprovalFor field, co-located in @loop-engine/actors. - TransitionResult.status widening to include pending_approval, plus requiresApprovalFrom?: ActorId. TransitionParams gains constraints? so the hook is reachable from engine.transition(). Scope guardrails per D-08 → A explicitly enumerated: no constraint DSL, no maxConsecutiveAITransitions, no full policy engine. Migration diff shows both the rename-only path (callers reading only authorized) and the opt-into-approval path for consumers who want to use the new hook. Packages bumped: actors (major), runtime (major), sdk (major). Amend note: the initial changeset commit for SR-007 inadvertently overwrote the SR-006 block because the StrReplace anchor used a partial view of the file that didn't include SR-006. Amended to restore SR-006 byte-for-byte from 7bdea78 (the original SR-006 commit) with SR-007 appended after. No push had occurred between the original and amended commit, so the amend does not rewrite shared history. Surface-Reconciliation-Id: SR-007
…e (D-03; MECHANICAL 8.9)
Widens ActorTypeSchema from 3 variants to 4 variants and adds a
first-class SystemActor interface, aligning the shipped actor
taxonomy with D-03's resolution. Replaces the pre-existing legacy
alias that silently downgraded `"system"` input to `"automation"`.
Piece 1: ActorTypeSchema union widens from
`["human", "automation", "ai-agent"]` to
`["human", "automation", "ai-agent", "system"]` at
packages/core/src/schemas.ts:37. Propagates through ActorRefSchema
(line 42 uses ActorTypeSchema directly) and TransitionSpecSchema
(line 65 uses z.array(ActorTypeSchema)). No caller-visible
signature changes beyond the enum variant addition.
Piece 2: SystemActor interface added to
packages/actors/src/types.ts, mirroring the HumanActor /
AutomationActor shape. Required field `componentId: string`
identifies the system component (reconciler, scheduler, etc.);
optional `version?: string` and inherited `ActorRef` fields
complete the shape. Paired SystemActorSchema Zod schema with the
same structure as HumanActorSchema / AutomationActorSchema.
Piece 3: Actor discriminated union extends from
`HumanActor | AutomationActor | AIAgentActor` to
`HumanActor | AutomationActor | AIAgentActor | SystemActor`.
TypeScript widens `actor.type` narrowing accordingly.
Piece 4 (F-PB-13 correction): ACTOR_ALIASES in
packages/loop-definition/src/builder.ts:78 updated from
`system: "automation"` to `system: "system"`. The pre-D-03
mapping was a legacy normalization that coerced `"system"` input
to `"automation"` because SystemActor didn't exist as a
first-class type. Post-D-03, `system` is a first-class ActorType
variant and the DSL alias table normalizes to itself. The error
message at builder.ts:87 already lists `system` as a valid input,
so no user-facing copy change is needed. The prompt row names
this correction as the examples-side / DSL-side portion of the
D-03 work.
Class 2 implementer-count verification.
- No exhaustive `switch (actor.type)` in source (rg
"switch.*actor[\.\[]['\"]?type" returned zero hits). Audit-
corrected framing per F-PB-13 confirmed.
- Equality-check consumers enumerated and reviewed:
* packages/guards/src/built-in/human-only.ts:8 — checks
`actor.type === "human"`. Unaffected by widening.
* packages/actors/src/authorization.ts:30 — checks
`actor.type === "ai-agent"`. Unaffected by widening.
* packages/observability/src/metrics.ts:47-48 — counts
ai-agent and human transitions. Unaffected by widening;
system transitions contribute to neither counter, which is
the correct behavior (they're neither AI nor human).
- ACTOR_ALIASES DSL entry at loop-definition/src/builder.ts:78
— updated same-commit per F-PB-13.
Two new tests added to packages/actors/src/__tests__/actors.test.ts:
- SystemActor schema validates componentId as required string.
- canActorExecuteTransition accepts system actor when allowed.
Actors suite grows 8 → 10 tests.
Workspace verification, all clean on first pass.
- pnpm -r build (preemptive per Observation A broad-fix
agreement) — clean. The rename+widening propagates through
core → actors → runtime → SDK via export * wildcards; all
four package dists rebuilt fresh. No Observation A
reproduction.
- pnpm -r typecheck — clean, 19 packages.
- pnpm -r test — clean on first pass, 142/142 tests (actors
8 → 10).
- pnpm typecheck:examples — clean (verified via prior pnpm -r
pass; in-tree examples use the DSL builder which now maps
system→system correctly).
- core/dist/index.d.ts — ActorTypeSchema declared as
z.ZodEnum<["human", "automation", "ai-agent", "system"]>.
- actors/dist/index.d.ts — SystemActor interface,
SystemActorSchema, widened Actor union all present; SDK
barrel flows them via `export * from '@loop-engine/actors'`.
- loop-definition/dist/index.cjs — ACTOR_ALIASES.system now
maps to "system".
- Tarball sizes: core 14.5 KB, actors 9.4 KB (+0.2 KB from
SystemActor + schema additions), runtime 13.1 KB, sdk 14.2
KB, loop-definition 16.6 KB — all under ceilings.
C-NN observations.
- Observation A did not reproduce because the preemptive
pnpm -r build (Observation A broad-fix default) rebuilt all
downstream dists including runtime, ahead of typecheck. This
is the procedure working as intended by the agreed-upon
Observation A broad-fix. First SR exercising the broad-fix
default; clean result confirms the fix is correct. Pending
operator's prompt draft to formalize `pnpm -r build` as the
Class 1 default (agent continues using it preemptively in
subsequent SRs until the prompt lands).
- SR-start gates (C-08 + C-10 extended): 6 stubs (baseline
unchanged), zero stale symlinks. Second SR running the
readlink -f resolution check; still clean.
- No new C-NN findings.
Status after SR-008.
- Phase A.1: 4/4 landed.
- Phase A.2: 4/4 landed (D-09, D-13, D-08, **D-03**). Phase
A.2 complete.
- Phase A.3: 6 rows pending (D-05 schema rewrite, D-05 builder
collapse, D-02 ID factories, D-01 ID helpers, D-13
re-homing, D-15 guard set confirm).
Surface-Reconciliation-Id: SR-008
Appends SR-008 section after SR-007. Covers the four D-03 pieces landed in 8e0a12f: - ActorTypeSchema widens from 3 enum variants to 4 (human | automation | ai-agent | system). - New SystemActor interface in @loop-engine/actors with componentId as the identifying field. - New SystemActorSchema Zod schema mirroring HumanActorSchema / AutomationActorSchema. - Actor discriminated union extends to include SystemActor. - ACTOR_ALIASES.system corrected from "automation" to "system" per F-PB-13 — legacy coercion that silently downgraded "system" input to "automation" at DSL validation is removed; system is now first-class. Surfaces the behavior change for DSL consumers who declared allowedActors: [system] pre-D-03: previously those transitions were tagged with "automation" at runtime; post-D-03 they're tagged "system". Migration block shows both the DSL-side preservation and the authorization-code adjustment for callers that relied on the coercion. Out-of-scope clarified: engine-internal consumers constructing SystemActor (wiring lives where the consumers live, not here), system-only guard primitives (no 1.0.0-rc.0 roadmap demand), system-transition metric counters (additive, deferred). Packages bumped: core (major), actors (major), loop-definition (major), sdk (major). Pre-edit file view confirmed structure integrity prior to StrReplace (Observation C discipline from SR-007 applied). Post-commit structure verified: 8 SR headers in order, comment anchor intact, file length 480 lines. Surface-Reconciliation-Id: SR-008
D-02 Phase A.3 row 1 (per PB-EX-06 Option A resolution — lands immediately before D-05 schema rewrite so that D-05's canonical OutcomeSpec.id?: OutcomeId signature can reference the brand at commit time). Additive Class 1: two new brand schemas in packages/core/src/schemas.ts. OutcomeIdSchema → OutcomeId brand for outcome identification at the D-05 rewrite's new OutcomeSpec.id optional field. CorrelationIdSchema → CorrelationId brand consumed by LoopInstance.correlationId and LoopEventBase in Branch B work (brand lands with no in-Branch-A consumer; mild spec-draft churn, harmless per PB-EX-06 analysis). Pattern follows every existing ID brand in this file (LoopIdSchema/AggregateIdSchema/etc): z.string().brand<"Name">() plus z.infer type alias. Placement: in the brand block between TransitionIdSchema and LoopStatusSchema. Both symbols propagate transparently through the SDK barrel via the existing export * from "@loop-engine/core" re-export; no barrel edits required. Class 1 additive verification per Pass B discipline: - pnpm -r build: green - C-10 post-build workspace symlink integrity scan: clean - pnpm -r typecheck: green across all packages - pnpm -r test: green, all tests passing - Core d.ts surface: OutcomeIdSchema, OutcomeId, CorrelationIdSchema, CorrelationId all present in packages/core/dist/index.d.ts - Core npm pack: 14.7 kB / 83.0 kB unpacked / 9 files (unchanged shape; modest additive size delta from two new 2-line brand definitions) - bd-forge-main F-01 stub baseline: 5 stubs (unchanged) Resolution log: API_SURFACE_DECISIONS_RESOLVED.md D-02 → A plus D-05 extension (PB-EX-06 Option A) at bd-forge-main commit c1d5d931. Spec draft: API_SURFACE_SPEC_DRAFT.md §1 OutcomeIdSchema and CorrelationIdSchema entries at lines 488-505 (brand additions). Surface-Reconciliation-Id: SR-009
Appends SR-009 section to the in-flight 1.0.0-rc.0 changeset covering the D-02 OutcomeIdSchema + CorrelationIdSchema additions. Documents: - Class 1 additive scope (two new brands in @loop-engine/core schemas.ts) - PB-EX-06 Option A row-order correction (D-02 precedes D-05 in reordered Phase A.3 sequence) - Migration examples showing opt-in branding for outcome and correlation identifiers pending D-01 factory helpers (SR-012) - Out-of-scope items (D-01 factories, D-05 OutcomeSpec.id field, LoopInstance.correlationId + LoopEventBase.correlationId propagation) - Symbol diff against 0.1.5 (four new exports; zero barrel edits required; transparent propagation via existing SDK export *) Surface-Reconciliation-Id: SR-009
…c/OutcomeSpec schemas (D-05; PB-EX-05 Option B; MECHANICAL 8.11) Implements D-05 schema rewrite: field renames (loopId/stateId/transitionId/guardId → id; allowedActors → actors; terminal → isTerminal), additive fields (StateSpec.isError, GuardSpec.failureMessage, OutcomeSpec.id?: OutcomeId, OutcomeSpec.measurable, LoopDefinition.domain), and TransitionSpec.signal optionality at the authoring layer (PB-EX-05 Option B). PB-EX-05 implementation: schema-level .transform() on TransitionSpecSchema fills signal := id when authored signal is absent, so the OUTPUT type has signal: SignalId required. Engine sites typecheck without per-site fallbacks per the resolution's runtime-no-modification promise. The two originally-named enforcement sites (LoopBuilder pre-fill, parser/registry-adapter applyAuthoringDefaults helper) are retained as defensive markers but now idempotent against the in-schema transform. Operator-ratifiable as a refinement of PB-EX-05's enforcement strategy; no contract semantics changed. PB-EX-06 sequencing followed: D-02 brands (SR-009) landed before this row, so OutcomeSpec.id?: OutcomeId resolves cleanly. Layered contract preserved: runtime fields (LoopInstance.loopId, TransitionRecord.transitionId, LoopStartedEvent.definition.loopId, GuardEvaluationResult.guardId, StartLoopParams.loopId, TransitionParams.transitionId) intentionally unchanged. Cascade across @loop-engine/core, /loop-definition, /registry-client, /runtime, /actors, /guards, /adapter-vercel-ai, /observability, /sdk, /events, /ui-devtools; apps/playground; scripts/validate-loops.ts; all schema-construction tests workspace-wide. Workspace -r build / typecheck / test all green (143/143 tests pass). Phase A.7 verification per SR-010 changeset entry. Surface-Reconciliation-Id: SR-010 BREAKING CHANGE: LoopDefinition / StateSpec / TransitionSpec / GuardSpec / OutcomeSpec field renames per D-05. See .changeset/1.0.0-rc.0.md SR-010 section for full migration guide and rename map.
…MECHANICAL 8.12)
Collapses the authoring-layer bridging logic now that D-05 schema field names match consumption-layer conventions (SR-010). Removes LoopBuilderGuardLegacy, LoopBuilderGuardShorthand, their union discriminator, ACTOR_ALIASES string-form-alias map, and normalizeActorType; simplifies LoopBuilderGuardInput to the single canonical shape Omit<GuardSpec, "id"> & { id: string }. LoopBuilderTransitionInput.actors tightens from string[] to ActorType[].
The signal := transition.id pre-fill in normalizeTransitions is explicitly retained as a defensive boundary marker for PB-EX-05 Option B (post-SR-010 enforcement-site amendment: canonical enforcement is the .transform() on TransitionSpecSchema; this marker is idempotent against it).
Barrel re-exports pruned in packages/loop-definition/src/index.ts and packages/sdk/src/index.ts. Tests updated to canonical forms — ai_agent underscore → ai-agent dash; guard shorthand (type/minimum) → canonical GuardSpec with parameters. No example-side follow-up needed (examples/ai-actors/shared/loop.ts already uses canonical forms).
Workspace -r build / typecheck / test all green (143/143 tests pass). D.ts surface diff confirms removed types absent. @loop-engine/loop-definition tarball shrinks 25.6 → 17.6 KB packed.
Surface-Reconciliation-Id: SR-011
BREAKING CHANGE: LoopBuilderGuardLegacy and LoopBuilderGuardShorthand types removed; guard-input shorthand form (type/minimum) no longer accepted — use canonical GuardSpec shape with explicit parameters. ai_agent underscore actor alias removed — use "ai-agent" dash form. See .changeset/1.0.0-rc.0.md SR-011 section for migration guide.
…d/signalId/stateId/actorId (D-01) Adds seven brand-cast factory functions to `@loop-engine/core` so consumers can construct branded ID values without inline `as XId` casts at every call site: - `loopId(s: string): LoopId` - `aggregateId(s: string): AggregateId` - `transitionId(s: string): TransitionId` - `guardId(s: string): GuardId` - `signalId(s: string): SignalId` - `stateId(s: string): StateId` - `actorId(s: string): ActorId` Each factory is a pure type-level cast — no runtime validation. If runtime validation is needed, the corresponding `*Schema.parse()` in `./schemas` remains the right tool. Per D-01 → A in `API_SURFACE_DECISIONS_RESOLVED.md`. The seven- factory enumeration is exact: `outcomeId` and `correlationId` factories are intentionally out of scope for this row even though the underlying brand schemas (`OutcomeIdSchema`, `CorrelationIdSchema`) shipped via SR-009. Those factories are deferred until SDK consumer experience surfaces a need. Net new files: - `packages/core/src/idFactories.ts` — the seven factories with JSDoc explaining rationale, runtime semantics, and scope. - `packages/core/src/__tests__/idFactories.test.ts` — 8 vitest cases (7 runtime-identity assertions + 1 type-level lock-in). `packages/core/src/index.ts` (barrel) gains a single re-export line. Propagates transparently through the SDK barrel via the existing `export * from "@loop-engine/core"` re-export. Phase A.7 verification: - `pnpm -r build`: green workspace-wide. - C-10 symlink scan: clean (pre + post build). - `pnpm -r typecheck`: green. - `pnpm -r test`: green workspace-wide; `@loop-engine/core` 15/15 (7 prior + 8 new). - d.ts surface diff: all seven factory exports present in `packages/core/dist/index.d.ts`; no other surface changes. - Tarball size for `@loop-engine/core`: 18.7 KB packed / 106.5 KB unpacked — well under the 500 KB ceiling. Surface-Reconciliation-Id: SR-012
…relocate EvidenceRecord to core (PB-EX-03 Option A; MECHANICAL 8.16 extended) Disambiguates the two functions that previously shared the name `guardEvidence` and relocates the shared `EvidenceRecord` type to `@loop-engine/core` per PB-EX-03 Option A. Changes: 1. Rename `@loop-engine/sdk`'s `guardEvidence` → `redactPiiEvidence`. Same behavior (hardcoded PII blocklist, prompt-injection prefix stripping, 512-char value length cap); file moves from `packages/sdk/src/lib/guardEvidence.ts` to `packages/sdk/src/lib/redactPiiEvidence.ts`. SDK barrel updated. 2. Relocate `EvidenceRecord` + `EvidenceValue` from `@loop-engine/sdk` to `@loop-engine/core` (new file `packages/core/src/evidence.ts`). Core barrel exports both via `export * from "./evidence"`. SDK's `redactPiiEvidence` imports `EvidenceRecord` from core. Invariants preserved: - `@loop-engine/core`'s `guardEvidence` primitive (generic redaction with `stripFields` + `maskPatterns`) is unchanged. - `ToolAdapter.guardEvidence` contract member is unchanged. - `adapter-perplexity`'s `guardEvidence` method continues to satisfy `ToolAdapter` without modification. Docs: `adapter-openclaw/loop-engine-governance/SKILL.md` updated to use `redactPiiEvidence` in import examples and prose. Verification: `pnpm -r build` + C-10 symlink scan + `pnpm -r typecheck` + `pnpm -r test` all clean. D.ts surface confirms SDK exports `redactPiiEvidence` (no `guardEvidence`, no `EvidenceRecord`); core exports `guardEvidence` primitive, `EvidenceRecord`, `EvidenceValue`. Originator: PB-EX-03 (guardEvidence name collision + EvidenceRecord placement); MECHANICAL 8.16 as extended by PB-EX-03 Option A. Surface-Reconciliation-Id: SR-013a
…3; PB-EX-02 Option A)
First of four AI provider adapter re-homings onto the `ActorAdapter`
contract landed in SR-006. Gemini is the lowest-blast-radius adapter
(near-conforming already), validating the return-shape pattern before
the heavier Anthropic/OpenAI rewrites.
Changes:
- `GeminiLoopActor` now `implements ActorAdapter`. Adds readonly
`provider: "gemini"` and `model: string` properties.
- `createSubmission(context: LoopActorPromptContext)` now returns
`AIAgentSubmission` (not the bespoke `GeminiActorSubmission`
wrapper). Return shape:
`{ actor, signal: signalId(decision.signalId), evidence: { reasoning,
confidence, dataPoints?, modelResponse } }`.
- `signal` is brand-cast via the `signalId()` factory (D-01, SR-012)
after validation against `context.availableSignals`.
- `actor.id` is brand-cast via the `actorId()` factory (D-01).
- Factory `createGeminiActorAdapter` return type widened to
`ActorAdapter` (concrete class still exported for users who need
the class type).
- `src/types.ts` drops `GeminiActorSubmission` and the `GeminiLoopActor`
duck-type alias — both were the pre-contract shape. Keeps
`GeminiLoopActorConfig` (factory options, already PB-EX-02 compliant).
PB-EX-02 Option A note: Gemini already shipped with construction-time
tuning (`modelId`, `maxOutputTokens`, `systemPrompt`,
`confidenceThreshold` all on `GeminiLoopActorConfig`), so there is no
per-call → factory migration to do — only the return-shape
normalization onto `AIAgentSubmission`.
Tests: 7/7 pass (6 prior + 1 new covering evidence shape). Prior tests
adjusted from `result.decision.*` to `submission.signal` /
`submission.evidence.*`. No test-intent changes.
Surface-Reconciliation-Id: SR-013b
…B-EX-02 Option A)
Second of four AI provider adapter re-homings onto the `ActorAdapter`
contract. Parallel to adapter-gemini (SR-013b/1): near-conforming
already, so the commit is return-shape normalization + `implements
ActorAdapter` + signal/actor-ID brand casts via D-01 factories.
Changes in adapter-grok:
- `GrokLoopActor` now `implements ActorAdapter`. Adds readonly
`provider: "grok"` and `model: string` properties.
- `createSubmission(context: LoopActorPromptContext)` returns
`AIAgentSubmission`: `{ actor, signal: signalId(decision.signalId),
evidence: { reasoning, confidence, dataPoints?, modelResponse } }`.
- `signal` brand-cast via `signalId()` factory (D-01, SR-012) after
validation against `context.availableSignals`.
- `actor.id` brand-cast via `actorId()` factory (D-01).
- Factory `createGrokActorAdapter` return type widened to `ActorAdapter`.
- `src/types.ts` drops `GrokActorSubmission` and the `GrokLoopActor`
duck-type alias — both were the pre-contract shape. Keeps
`GrokLoopActorConfig` (factory options, already PB-EX-02 compliant).
Changes in adapter-openclaw documentation:
- `loop-engine-governance/example-fraud-review-grok.ts` updated to
destructure `submission.signal` / `submission.evidence.*` instead of
the pre-contract `decision.*` shape. Example is not in the
compile-gated source set (adapter-openclaw tsconfig.json includes
src/** only); update is documentation hygiene to keep the skill
example consistent with the shipped contract.
PB-EX-02 Option A note: Grok already shipped with construction-time
tuning (`modelId`, `maxTokens`, `systemPrompt`, `confidenceThreshold`,
`baseURL` on `GrokLoopActorConfig`), so there is no per-call → factory
migration to do — only the return-shape normalization onto
`AIAgentSubmission`.
Tests: 6/6 pass. Existing test `returns decision with valid signalId`
rewritten to assert `submission.signal` + `submission.evidence.*` with
the same covering intent.
Surface-Reconciliation-Id: SR-013b
…mpt/signal handling (D-13; PB-EX-02 Option A)
Third of four AI provider adapter re-homings onto the `ActorAdapter`
contract — the first of the two "full internal rewrite" adapters
that PB-EX-02 Option A explicitly sanctioned. Anthropic previously
took a bespoke `createSubmission(params)` with caller-supplied
`signal`, `actorId`, and `prompt`; after this commit it takes
`createSubmission(context: LoopActorPromptContext)` and internalizes
the prompt-construction, signal-selection, and actor-ID-generation
responsibilities.
Input-contract changes:
- `createSubmission` signature: `(params: CreateAnthropicSubmissionParams)`
→ `(context: LoopActorPromptContext)`. `CreateAnthropicSubmissionParams`
is removed from the public surface.
- `AnthropicActorAdapter` interface removed; `createAnthropicActorAdapter`
now returns `ActorAdapter` directly.
- Per-call `maxTokens` and `temperature` move from submission-params
into construction-time `AnthropicActorAdapterOptions` per PB-EX-02
Option A (provider-specific tuning belongs at construction time, not
per-call).
- Per-call `signal`, `actorId`, `prompt`, `displayName`, `metadata`,
and `dataPoints` removed from the input contract — the contract is
`LoopActorPromptContext` as the sole input, per D-13.
Internal rewrite (the "keep prompt-building intentionally minimal"
path):
- Prompt construction moved inside the adapter: a minimal system
prompt (4 lines, JSON-schema-described output) plus a user prompt
that enumerates `currentState`, `availableSignals`, `evidence`,
and `instruction` from the context. Pattern mirrors adapter-gemini
and adapter-grok; intentionally NOT a prompt-design optimization
pass.
- Signal selection internalized: the model returns `signalId` in its
JSON response; adapter validates against `context.availableSignals`
before branding via the `signalId()` factory (D-01, SR-012).
Parallel to Gemini/Grok pattern.
- Actor ID generation internalized: `actor.id = actorId(crypto.randomUUID())`.
Callers no longer thread actor IDs through per-call params.
- `displayName` / `metadata` on the actor: dropped. `LoopActorPromptContext`
does not carry these, and PB-EX-02 Option A narrows the contract.
Callers who need them can post-process the returned submission or
surface via construction-time options in a follow-up if the
consumer experience demands it.
Output-contract invariants (unchanged):
- Return type remains `AIAgentSubmission`; shape unchanged from the
pre-re-home version (`{ actor, signal, evidence }`).
- `buildAIActorEvidence` helper usage preserved; promptHash computed
over the full `${systemPrompt}\n${userPrompt}` for traceability.
Docs: `adapter-openclaw/loop-engine-governance/example-ai-replenishment-claude.ts`
updated to match shipped factory signature (the example was pointing
at a hypothetical `(apiKey, { modelId, confidenceThreshold })`
signature that never existed — corrected to the actual
`{ apiKey, model }` options shape) and to destructure
`submission.signal` / `submission.evidence.*`.
Tests: 8/8 pass (6 prior + 2 new). Added: "throws when parsed
signalId is outside availableSignals" and "uses construction-time
maxTokens and temperature when provided". Existing tests adjusted
from per-call params shape to `LoopActorPromptContext`; response
mocks gain `signalId` field to match the new contract.
Surface-Reconciliation-Id: SR-013b
…/signal handling (D-13; PB-EX-02 Option A)
Fourth and final AI provider adapter re-homing onto the `ActorAdapter`
contract, completing SR-013b. Structurally parallel to adapter-anthropic
(SR-013b/3): OpenAI previously took a bespoke `createSubmission(params)`
with caller-supplied `signal`, `actorId`, and `prompt`; after this
commit it takes `createSubmission(context: LoopActorPromptContext)` and
internalizes the prompt-construction, signal-selection, and
actor-ID-generation responsibilities.
Input-contract changes:
- `createSubmission` signature: `(params: CreateOpenAISubmissionParams)`
→ `(context: LoopActorPromptContext)`. `CreateOpenAISubmissionParams`
is removed from the public surface.
- `OpenAIActorAdapter` interface removed; `createOpenAIActorAdapter`
now returns `ActorAdapter` directly.
- Per-call `maxTokens` and `temperature` move from submission-params
into construction-time `OpenAIActorAdapterOptions` per PB-EX-02
Option A (provider-specific tuning belongs at construction time, not
per-call).
- Per-call `signal`, `actorId`, `prompt`, `displayName`, `metadata`,
and `dataPoints` removed from the input contract — the contract is
`LoopActorPromptContext` as the sole input, per D-13.
Internal rewrite (the "keep prompt-building intentionally minimal"
path):
- Prompt construction moved inside the adapter: the same minimal
system/user prompt pattern as adapter-anthropic (SR-013b/3) and
adapter-gemini/adapter-grok. Not a prompt-design optimization pass.
- Signal selection internalized: the model returns `signalId` in its
JSON response; adapter validates against `context.availableSignals`
before branding via the `signalId()` factory (D-01, SR-012).
- Actor ID generation internalized: `actor.id = actorId(crypto.randomUUID())`.
- `displayName` / `metadata` on the actor: dropped, consistent with
adapter-anthropic (SR-013b/3).
Output-contract invariants (unchanged):
- Return type remains `AIAgentSubmission`; shape unchanged from the
pre-re-home version.
- `buildAIActorEvidence` helper usage preserved; promptHash computed
over the full `${systemPrompt}\n${userPrompt}`.
Docs: `adapter-openclaw/loop-engine-governance/example-infrastructure-change-openai.ts`
updated: factory signature corrected to `{ apiKey, model }` (was
pointing at a hypothetical `(apiKey, { modelId, confidenceThreshold })`
signature); destructure updated to `submission.signal` /
`submission.evidence.*`.
Tests: 8/8 pass (6 prior + 2 new). Same additions as adapter-anthropic:
"throws when parsed signalId is outside availableSignals" and "uses
construction-time maxTokens and temperature when provided". Existing
tests adjusted from per-call params shape to `LoopActorPromptContext`;
response mocks gain `signalId` field to match the new contract.
SR-013b closing: four AI provider adapters (Gemini, Grok, Anthropic,
OpenAI) now conform to `ActorAdapter`. The fifth historically
"AI adapter" — `adapter-vercel-ai` — ships under the `IntegrationAdapter`
archetype per PB-EX-07 Option A and is out of SR-013b scope.
Surface-Reconciliation-Id: SR-013b
Appends the SR-013b section to the rolling `1.0.0-rc.0` changeset covering the four AI provider adapter re-homings onto `ActorAdapter` landed in commits 52fe962 (gemini), ba95b76 (grok), b5e6c6d (anthropic), and 951afa0 (openai). Frontmatter extended: all four AI adapter packages (`@loop-engine/adapter-gemini`, `@loop-engine/adapter-grok`, `@loop-engine/adapter-anthropic`, `@loop-engine/adapter-openai`) now bump as `major` per D-07's no-alias policy — any consumer that imported `CreateAnthropicSubmissionParams`, `CreateOpenAISubmissionParams`, the `AnthropicActorAdapter`/`OpenAIActorAdapter` interfaces, the `GeminiActorSubmission`/`GrokActorSubmission` return types, or relied on per-call `maxTokens`/`temperature` params (Anthropic/OpenAI) needs a code change per the migration examples in the changeset body. Documentation only; no code or test changes. Per-adapter source changes are in the four SR-013b commits referenced above. Surface-Reconciliation-Id: SR-013b
Confirm-pass rather than edit-pass. Per D-15 Option C, the `1.0.0-rc.0` built-in guard set is the four generic guards already registered in `packages/guards/src/registry.ts:21-26`: `confidence-threshold`, `human-only`, `evidence-required`, `cooldown`. All four satisfy the "generic across domains" pruning rule on inspection: - `confidence-threshold` is parameterized on a numeric threshold and reads `evidence.confidence`; no domain coupling. - `human-only` is a pure `actor.type === "human"` check; no domain coupling. - `evidence-required` validates caller-specified `requiredFields`; guard logic is domain-agnostic field-presence. - `cooldown` is pure time-based rate-limiting on `loopData.lastTransitionAt`; no domain coupling. No pruning required. The borderline candidates named in the resolution log (`field-value-constraint`, `duplicate-check-passed`) and earlier candidates once considered (`actor-has-permission`, `approval-obtained`, `deadline-not-exceeded`) do not exist in source and are not added for `1.0.0-rc.0`. No source changes. `@loop-engine/guards` source is unchanged; `defaultRegistry` already exposes exactly the confirmed set. This commit records the confirmation in the coordinated changeset narrative only; no package bump is added for `@loop-engine/guards` because there is no observable change to its public surface. Phase A.3 closes with this SR. Phase A.4 opens next (R-164 barrel rewrite, D-21 single-root export enforcement). Surface-Reconciliation-Id: SR-014
Replaces five `export *` re-export patterns in `@loop-engine/sdk`'s
root barrel with explicit named re-exports, per Class 3 (C-03)
publish hygiene calibration and spec §4 "Internal: SDK star
re-exports converted to named" (R-164). Rides in three additional
mechanical alignments that naturally fit R-164's barrel scope:
1. SDK `AIActor` tightening (observation-tier follow-up from SR-013b,
scheduled into R-164's barrel scope per operator guidance). The
historical loose interface
`interface AIActor { createSubmission: (...args: unknown[]) => Promise<unknown> }`
is replaced with `type AIActor = ActorAdapter`, where `ActorAdapter`
is the D-13 contract at `@loop-engine/core`. All four provider
adapters (`adapter-{anthropic,openai,gemini,grok}`) were re-homed
onto `ActorAdapter` in SR-013b, so this tightening is mechanical.
2. D-19 surface completeness fix. `parseLoopJson` and
`serializeLoopJson` are in D-19's ship list (resolution log row
D-19 → A) but were previously reachable only via the
`@loop-engine/sdk/dsl` subpath. Both are now added to the root
barrel, closing a prior-SR gap where the root barrel did not
match D-19's stated public surface. The `/dsl` subpath itself is
removed by the R-186 commit that follows per D-21.
3. D-17 enforcement. Nine `createLoop*Event` factories
(`createLoopCancelledEvent`, `createLoopCompletedEvent`,
`createLoopFailedEvent`, `createLoopGuardFailedEvent`,
`createLoopSignalReceivedEvent`, `createLoopStartedEvent`,
`createLoopTransitionBlockedEvent`,
`createLoopTransitionExecutedEvent`,
`createLoopTransitionRequestedEvent`) were previously surfaced
through `export * from "@loop-engine/events"` despite D-17 → A
ruling them internal-only. The explicit-named rewrite naturally
omits them (spec §4 line 1582 "Internal: createLoop*Event
factories"). Runtime continues to consume them via direct
import from `@loop-engine/events`.
Class 3 gate (pre/post d.ts symbol-level diff):
- ADDED (2): parseLoopJson, serializeLoopJson → cite D-19.
- REMOVED (9): all createLoop*Event factories → cite D-17 / spec §4.
- Net count: 158 → 151 symbols.
- Every delta cites a resolution-log decision or a spec §4 entry.
No undocumented drops or adds.
Pre-existing public symbols preserved 1:1 through the rewrite:
the remaining 149 symbols shipped both before and after (modulo
the pre-rewrite dist including GuardRegistry twice — once via
explicit re-export, once via `export * from guards` — which
collapses to a single declaration in the explicit-named form).
AIActor shape-tightening is internal-consistent: the updated
`AdapterFactory` return type (`AIActor = ActorAdapter`) accepts
all four provider-adapter factories unchanged, since they already
return `ActorAdapter` post-SR-013b.
Verification:
- `pnpm --filter @loop-engine/sdk typecheck` → clean.
- `pnpm --filter @loop-engine/sdk test` → 9/9 passed.
- `pnpm -r typecheck` → clean across workspace.
- `pnpm -r test` → clean across workspace.
Surface-Reconciliation-Id: SR-015
…tool-bridge `packages/adapter-vercel-ai/src/loop-tool-bridge.ts` used the pre-D-05 field accessors `definition.id` and `transition.id` in five sites. These were missed by the D-05 schema rewrite cascade (commit `4b8035d`, "feat(core)!: rewrite LoopDefinition/ StateSpec/TransitionSpec/... schemas"), which renamed: - `LoopDefinition.id` → `LoopDefinition.loopId` - `TransitionSpec.id` → `TransitionSpec.transitionId` Surfaced during SR-015 Phase A.7 verification. The failure was silently masked in prior SR logs for two reasons: 1. `tsup` emits `.js`/`.cjs` successfully before its dts step fails, so `pnpm -r build` shows the package as built even when the d.ts worker throws (the dist index.d.ts was missing in this package). 2. Prior SR verification log output was truncated with `tail`, which elided the adapter-vercel-ai typecheck failure line from view. This is a procedural-tier finding against earlier SR-012 and SR-010-level verification, not against any locked decision. Calibration learning will be logged into the bd-forge-main execution log as part of SR-015's findings. Verification: - `pnpm --filter @loop-engine/adapter-vercel-ai typecheck` → clean (was failing with TS2339 × 5). - `pnpm --filter @loop-engine/adapter-vercel-ai build` → emits `dist/index.d.ts` (previously missing). Surface-Reconciliation-Id: SR-015
Removes the `@loop-engine/sdk/dsl` subpath export from the SDK
package. Per D-21 → A (locked in `API_SURFACE_DECISIONS_RESOLVED.md`),
every `@loop-engine/*` package must declare only root exports,
with `@loop-engine/registry-client/betterdata` as the single
sanctioned exception. The SDK's `/dsl` subpath was a pre-D-21
transitional entry point that anticipated D-18's future
`@loop-engine/loop-definition` → `@loop-engine/dsl` package
rename; under D-21 hygiene it does not belong on the SDK's
package boundary.
Changes:
1. `packages/sdk/package.json` — drop `./dsl` from `exports`
map; only `"."` root export remains.
2. `packages/sdk/tsup.config.ts` — drop `src/dsl.ts` from the
`entry` array so no `dist/dsl.*` artifacts are produced.
3. `packages/sdk/src/dsl.ts` — deleted (was two lines:
`export * from "@loop-engine/loop-definition"`).
4. `apps/playground/package.json` — add
`@loop-engine/loop-definition` as an explicit workspace
dependency.
5. `apps/playground/src/app/page.tsx` — migrate
`import { parseLoopYaml } from "@loop-engine/sdk/dsl"` to
`import { parseLoopYaml } from "@loop-engine/loop-definition"`.
Playground migration rationale: the `/dsl` subpath was serving
as a de-facto browser-safe narrow entry point because the SDK
root barrel transitively imports Node-only modules (`node:module`
via `createRequire` in `ai.ts`, `node:fs` via
`@loop-engine/registry-client`). Importing the DSL parser from
the SDK root fails Next.js/webpack bundling with
`Can't resolve 'fs'` / `'module'`. Per D-21's spirit (the SDK
is the aggregate consumption layer; narrow-slice consumers go
direct to source packages), browser consumers migrate to
`@loop-engine/loop-definition`. This also anticipates D-18's
future state where that package renames to `@loop-engine/dsl`
as a standalone published surface.
Class 3 package-level surface diff (R-186 only; root
`dist/index.d.ts` surface unchanged from post-R-164):
- ADDED (0).
- REMOVED (1): `applyAuthoringDefaults`.
`applyAuthoringDefaults` was previously reachable only via the
`/dsl` subpath's `export * from "@loop-engine/loop-definition"`.
It is not on D-19's `1.0.0-rc.0` ship list (resolution log D-19
→ A enumerates `LoopBuilder`, `parseLoopYaml`, `parseLoopYamlSafe`,
`serializeLoopYaml`, `parseLoopJson`, `serializeLoopJson`,
`validateLoopDefinition` — `applyAuthoringDefaults` is absent).
The symbol remains exported from `@loop-engine/loop-definition`
directly (it's consumed cross-package by
`@loop-engine/registry-client`); removing it from SDK closes a
prior leak through `export *` that D-19's spec correctly
omitted. A corresponding §4 "Internal: applyAuthoringDefaults"
entry lands in `API_SURFACE_SPEC_DRAFT.md` via bd-forge-main.
D-21 audit result: after this commit, zero `@loop-engine/*`
packages declare non-root subpath exports except the sanctioned
`@loop-engine/registry-client/betterdata`. R-186 converged at
single-root-hygiene.
Verification:
- `pnpm -r typecheck` → exit 0 (all packages).
- `pnpm -r test` → exit 0.
- `pnpm -r build` → exit 0 (including `apps/playground`
Next.js build, which was previously broken by the transitive
Node-only imports when migrated to the SDK root).
- `pnpm --filter @loop-engine/sdk pack --dry-run` → 16.8 kB
packed / 91.8 kB unpacked; well under package ceiling.
- C-10 symlink scan → clean.
- D-21 audit (every `@loop-engine/*` package's `exports` map)
→ single-root compliant; only sanctioned
`registry-client/betterdata` subpath remains.
Surface-Reconciliation-Id: SR-015
…-root enforcement Narrative-only changeset addition for SR-015. Package bumps already listed in the frontmatter (sdk + adapter-vercel-ai both already 'major' from prior SRs); no new frontmatter entries. Surface-Reconciliation-Id: SR-015
SR-016.1 — the integration-test-infrastructure gate that unblocks all
subsequent SR-016 sub-commits. Before this commit, `adapter-postgres`
had zero test coverage and no way to exercise a real Postgres instance
from the test runner. SR-016's discipline explicitly disallows mocking
`pg` for the production-grade D-12 work: transaction isolation,
migration idempotency, connection pooling exhaust-and-recover,
constraint-violation error-mapping, and connection-loss mid-operation
handling cannot be meaningfully verified against a mock.
Per operator adjudication on the six SR-016 design decisions:
- Container runtime: testcontainers over docker-compose.
Rationale: per-test-file spin-up + automatic teardown gives
clean isolation; Compose would force shared-state coupling.
The dep lives in `adapter-postgres/devDependencies`, not at
the workspace root — future packages that need integration
tests decide independently.
- Image matrix: Postgres 15-alpine + 16-alpine. The adapter is
documented to support Postgres 13+ (floor); 15 and 16 are the
actively-tested matrix (15 = conservative production default,
16 = current latest).
Changes in this sub-commit:
1. `package.json` — add `@testcontainers/postgresql@^11.14.0`,
`@types/pg@^8.20.0`, `pg@^8.20.0`, `vitest@^1.6.1` as
devDependencies; add `"test": "vitest run"` script. The `pg`
runtime dep stays at `peerDependencies: ^8.0.0` (consumer
supplies it); the devDep copy is solely for the test helper
to construct its own `pg.Pool`.
2. `vitest.config.ts` — new. Sets `testTimeout: 120_000` and
`hookTimeout: 120_000` (image-pull on first run can exceed the
default 5s ceiling). Uses `pool: "forks"` + `singleFork: true`
to serialize matrix describes across the two Postgres versions,
keeping Docker daemon bandwidth predictable for CI environments
with tighter resource ceilings.
3. `src/__tests__/helpers/postgres.ts` — new. Exports three
functions consumed by integration tests:
- `assertDockerAvailable()` — runs `docker version` with a
5s timeout and throws a multi-line SR-016-specific
diagnostic if the daemon is unreachable. Fail-loud by
design: the integration-test gate either proves the
infrastructure works or halts visibly. No mock fallback.
- `propagateDockerHost()` — portability shim for hosts where
the active `docker context` socket lives outside
testcontainers' default probe list. Reads
`docker context inspect --format '{{.Endpoints.docker.Host}}'`
and sets `DOCKER_HOST` if unset. Additionally, for VM-backed
runtimes (Colima, Podman, Rancher Desktop, OrbStack in VM
mode) whose host-side socket path does not exist inside the
Linux VM, sets `TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=
/var/run/docker.sock` so the reaper sidecar mounts the
VM-internal conventional path instead of the unreachable
host-side one. Safe on Docker Desktop (no-op behaviorally
since the default probe already finds the socket).
- `startPostgres(image, poolOverrides?)` — spins a
`PostgreSqlContainer`, returns a connected `pg.Pool` plus
a `teardown()` thunk that closes the pool before stopping
the container. `poolOverrides` lets future sub-commits
(SR-016.4) exercise non-default `max`, `idleTimeoutMillis`,
`connectionTimeoutMillis` without per-test container
reconfiguration.
4. `src/__tests__/smoke.test.ts` — new. The infrastructure gate
itself. Uses `describe.each(POSTGRES_IMAGE_MATRIX)` to run the
same four assertions against both matrix versions:
- Docker daemon reachable + container spins up (explicit
assertion so the gate appears as a visible line in test
output, not an implicit side effect of `beforeAll`).
- `pg.Pool` structurally satisfies `PgPoolLike` (adapter's
input contract). If this drifts, SR-016 halts for
adjudication.
- `createSchema` provisions `loop_instances` +
`loop_transitions` tables.
- `createSchema` is idempotent (second call is a no-op).
This invariant backs SR-016.2's future migration
versioning, where the first migration must re-apply
cleanly on a partially-migrated instance.
Explicitly NOT a functional test of `LoopStore` methods —
those land in SR-016.2 through SR-016.5.
Local verification (Colima + docker 29.4.1 client / 29.2.1 server):
- `pnpm --filter @loop-engine/adapter-postgres test`
→ 8/8 green in 2.62s (4 assertions × 2 matrix versions).
Re-run after cold daemon restart: 8/8 green in 2.61s.
- Fail-loud path verified: stopped Colima; re-ran the test;
the SR-016-specific diagnostic fired in 387ms with a clear
remediation path, not a cryptic testcontainers error after
a 53-second wait. Confirmed by inspection of the error
message: `[@loop-engine/adapter-postgres] Docker daemon is
not reachable ...`
- `pnpm --filter @loop-engine/adapter-postgres build` clean
(CJS + ESM + d.ts all emitted). C-14 full-stream scan
clean against the build output. Typecheck clean.
No source changes to `src/index.ts` — the adapter's public API is
unchanged by this sub-commit. No changeset entry added; SR-016's
consolidated changeset lands in SR-016.7 after all sub-commits are
merged.
Surface-Reconciliation-Id: SR-016
…ations tracking SR-016.2 — production-grade migration versioning per operator adjudication of the six SR-016 design decisions. Replaces the one-shot `createSchema` that issued two `CREATE TABLE IF NOT EXISTS` statements with a content-addressed, versioned, advisory- lock-serialized migration runner backed by raw SQL files on disk. Per operator decision #2: raw SQL + custom runner over `node-pg-migrate` / `umzug` / equivalent framework tools. The adapter owns two domain tables forever unless D-12 scope expands dramatically; framework tools have opinions buried in configuration that take longer to understand than the problem they solve. A ~200-LOC runner (including the generous documentation block) is reviewable and operator-auditable in an afternoon. Changes in this sub-commit: 1. `src/migrations/sql/` — three SQL migration files: - `001_schema_migrations.sql` creates the runner's tracking table. Load-bearing bootstrap migration: the runner tolerates its absence on first run (checks `information_schema.tables` before SELECTing from it) and thereafter treats every migration as go-through-the-table. - `002_loop_instances.sql` — the `loop_instances` domain table, previously inlined in `createSchema`. - `003_loop_transitions.sql` — the `loop_transitions` domain table, previously inlined in `createSchema`. All three use `CREATE TABLE IF NOT EXISTS` as a belt-and-suspenders guard against the narrow window between SQL application and INSERT INTO `schema_migrations` — a crash there leaves the table created but unrecorded, and a subsequent run re-applies the DDL (harmless) and records it. 2. `src/migrations/runner.ts` — `runMigrations`, `loadMigrations`, and supporting types. Invariants: - Idempotent: each migration recorded in `schema_migrations` after first application; subsequent runs return it under `skipped`. - Transactional: each migration applies in its own transaction; the DDL and the recording INSERT commit atomically. - Concurrent-safe: a Postgres advisory lock (`pg_advisory_lock($key::bigint)`) serializes concurrent callers. Second caller blocks, then sees every migration recorded and returns `applied: []`. - Drift-protected: each migration's SQL content is hashed with SHA-256 and recorded alongside. If a recorded migration's current on-disk checksum no longer matches the recorded one, the runner refuses to proceed — guarding against the foot-gun of editing an applied migration. 3. `src/index.ts`: - `PgPoolLike` widened with `connect(): Promise<PgClientLike>` (previously only required `query`). Non-breaking for every real consumer: `pg.Pool` structurally satisfies the widened shape. SR-016.1's smoke test explicitly verified this compatibility against the matrix and that assertion now covers the widened surface. - New `PgClientLike` type export — narrow duck-typed view of `pg.PoolClient` (adds `release(err?)`). Required by the migration runner and (forthcoming in SR-016.3) the transaction helper. - `createSchema` retained as backward-compatible alias; internally delegates to `runMigrations(pool)`. The public shape `(pool: PgPoolLike) => Promise<void>` is unchanged — only the behavior expands (now also provisions `schema_migrations`). - New exports: `runMigrations`, `loadMigrations`, and types `Migration`, `MigrationRunResult`, `RunMigrationsOptions`. 4. `tsup.config.ts`: - `shims: true` — provides `__dirname` in ESM output and `import.meta.url` in CJS output. The runner uses `__dirname + "/sql"` to locate migration files at runtime in both module formats; without the shim, the ESM build would fail at runtime with "__dirname is not defined" on first `runMigrations` call. - `onSuccess` hook copies `src/migrations/sql/` → `dist/migrations/sql/`. Tsup does not copy non-code assets by default; this ensures the shipped package can locate migrations at runtime. 5. `src/__tests__/migrations.test.ts` — seven integration tests against Postgres 16 (version-independent runner logic; SR-016.1's smoke test already proved infra-level 15-vs-16 compatibility): - Fresh DB: all three migrations apply. - Forward idempotency: second call is a no-op. - Bootstrap tolerance: `schema_migrations` absence on first call is handled correctly. - Partial-state recovery: pre-existing `loop_instances` table (operator applied direct DDL) is absorbed by `CREATE TABLE IF NOT EXISTS` without error. - Concurrent advisory-lock serialization: three concurrent `runMigrations` calls produce exactly one applier and two skippers. - Checksum drift detection: corrupted recorded checksum surfaces a loud, actionable error. - `loadMigrations` audit API: exposes the on-disk view for consumer introspection (non-applying). 6. `src/__tests__/smoke.test.ts`: two SR-016.1 assertions updated to reflect the new canonical three-table schema (`loop_instances`, `loop_transitions`, `schema_migrations`). The `createSchema` behavior change is real — the schema surface genuinely grew by one table — not a test authoring drift. 7. `README.md` — adds a "Schema migrations" section documenting the new `runMigrations` API, the four runner properties (idempotent / transactional / concurrent-safe / drift- protected), and the list of shipped migrations. The existing "Quick Start" still shows `createSchema(pool)` as it remains the batteries-included entry point. Local verification: - `pnpm --filter @loop-engine/adapter-postgres test` → 15/15 green in 4.49s (7 migration + 8 smoke). - `pnpm --filter @loop-engine/adapter-postgres build` clean. C-14 full-stream scan against build output: clean (after excluding the pre-existing `.npmrc`-interpolation WARN noise flagged as observation O-SR016.1-2). - `pnpm --filter @loop-engine/adapter-postgres typecheck` clean. - `pnpm -r build` (workspace-wide): clean. `apps/playground` builds cleanly (D-21 cascade guard from SR-015 holds). Workspace-wide C-14 scan: clean. C-14 halt-catch worth naming: the first build attempt failed on an unused `@ts-expect-error` directive in the runner's `defaultMigrationsDir` helper (`@types/node` provides ambient `__dirname`, making the directive unnecessary). The CJS + ESM emits reported "Build success" but the d.ts worker failed — exactly the tsup partial-success pattern C-14 was calibrated to catch. Fixed before commit. No changeset entry in this sub-commit; SR-016's consolidated changeset lands in SR-016.7 after all sub-commits are merged. The `PgPoolLike` widening, new `PgClientLike` export, new runner API, and expanded `createSchema` behavior will all be captured there. Surface-Reconciliation-Id: SR-016
…re sequencing
SR-016.3: adapter-internal `withTransaction(fn)` on `postgresStore`.
`fn` receives a `TransactionClient` (LoopStore-shaped) whose methods
route through a pg-acquired client inside BEGIN/COMMIT. fn resolution
commits; fn rejection rolls back and propagates the original error.
`TransactionClient` exposes LoopStore methods only — no raw
`pg.PoolClient` escape hatch, per PB-EX-02 Option A layering
discipline (provider-specific concerns stay in provider-specific
factories; consumers needing LISTEN/NOTIFY manage their own pg.Pool).
Nesting via the outer store is independent by design — inner calls
acquire their own client. Atomicity across nested scopes is achieved
by passing the outer `tx` down and calling its LoopStore methods.
Factored the five LoopStore method bodies into a shared
`buildLoopStoreAgainst(querier)` helper bound once to the pool
(non-transactional path) and once to the client (transactional path),
so the query layer is bit-for-bit identical across both paths.
Eight integration tests against Postgres 16 cover: commit,
rollback-on-user-error, rollback-on-database-error (pg error mid-tx),
return-value propagation, cross-client isolation during fn,
post-rollback pool recovery, nested-via-store independence, and the
tx-passthrough pattern for extending atomicity.
In-SR substantive finding resolved: `asLoopInstance` and
`asTransitionRecord` pre-existed SR-016 with a broken
TIMESTAMPTZ round-trip (`asString(Date) → "" → new Date("") →
.toISOString()` threw RangeError on any `getInstance` call after a
save). The adapter had zero tests when originally authored, so the
bug shipped unexercised. SR-016.3's withTransaction suite forced the
`saveInstance → getInstance` round-trip and surfaced it.
Fixed by introducing an `asIsoString` helper that accepts both pg's
default `Date` type-parser output and explicit string overrides.
Surface-Reconciliation-Id: SR-016
SR-016.4: `createPool(options?)` wraps `pg.Pool` construction with the four opinionated defaults adjudicated at the SR-016 six-decision gate: - max: 10 - idleTimeoutMillis: 30_000 - connectionTimeoutMillis: 5_000 - statement_timeout: 30_000 Defaults are exported as `DEFAULT_POOL_OPTIONS` for introspection. `PoolOptions` extends `pg.PoolConfig` with a first-class `statement_timeout` numeric field so consumers don't have to know about the libpq `options: '-c statement_timeout=N'` incantation. `statement_timeout` is wired via the `options` connection parameter rather than a per-connect `SET` handler — applied at connection-init, no round-trip, no timing window where a client could be returned to the consumer before the GUC lands. A consumer-supplied `options` string is preserved and composed with the statement_timeout clause. Seven integration tests: defaults-applied smoke, consumer override, statement_timeout server-side enforcement (cancels pg_sleep with SQLSTATE 57014), `options`-string preservation (verifies application_name GUC round-trips to current_setting), connectionTimeoutMillis enforcement on pool saturation, and the core exhaust-and-recover scenario (max=2, hold both clients, queue a third connect, release one held client, verify queued connect resolves with a usable connection). Adds `connectionString` to the `PostgresTestContext` helper shape so the pool tests can construct their own `pg.Pool` with per-test config without duplicating container-config wiring. Existing smoke, migrations, and transaction tests continue to use the shared `pool` field. README gains a "Pool configuration" section documenting defaults, rationale per knob, and override patterns. Quick Start now shows the recommended `createPool` + `runMigrations` flow; the raw-`pg.Pool` path is still supported for consumers who want full control. Surface-Reconciliation-Id: SR-016
…handling
SR-016.5: error-classification surface + connection-loss handling in
withTransaction. Resolves against the operator's pre-stored leans on
each of the four design surfaces.
Error classification (minimal-wrapper posture):
- `PostgresStoreError` base class with `.cause` + `.kind`
discriminant ("transient" | "permanent" | "unknown"). Not thrown
directly; carries retry classification for adapter-originated
errors.
- `TransactionIntegrityError` subclass (kind always "transient")
thrown by withTransaction when terminal state is indeterminate.
- `classifyError(err)` / `isTransientError(err)` exported for
consumer retry logic.
- Routine pg errors pass through unchanged; no per-SQLSTATE typed
errors shipped at RC (operator lean). Constraint violations,
data errors, etc. surface with `.code` intact.
Transient allowlist (deliberately narrow):
- Node connection errors: ECONNRESET, ECONNREFUSED, ETIMEDOUT,
ENOTFOUND, EHOSTUNREACH, ENETUNREACH.
- Postgres server lifecycle: 57P01, 57P02, 57P03.
- Deadlocks: 40P01.
- pg's "Connection terminated" message-matched errors (no code).
40001 (serialization_failure) deliberately excluded at RC because
the adapter doesn't ship a SERIALIZABLE opt-in yet; will be added
when the isolation-level surface lands.
withTransaction rule (indeterminacy-driven):
- fn threw + ROLLBACK succeeded → pass through fn's error.
- fn threw + ROLLBACK failed → TransactionIntegrityError with
fn error as cause (state indeterminate).
- fn succeeded + COMMIT failed with connection error →
TransactionIntegrityError with commit error as cause
(may have committed server-side, no ACK).
- fn succeeded + COMMIT failed with non-connection error →
pass through the commit error (Postgres auto-rolled-back
on deferred constraint / etc.).
Substantive finding surfaced + resolved in-SR: pg clients emit async
`'error'` events for server-side FATAL messages (57P01 backend
termination) delivered between query round-trips. Unhandled, these
become uncaught exceptions that can crash the consumer's process.
Discovered while authoring the mid-tx connection-loss test: the
first test run surfaced an unhandled exception even though the
test itself would have passed. withTransaction now installs a no-op
`'error'` handler for the lifetime of its checked-out client (the
error still reaches the query rejection path, which the wrapping
rule handles correctly). `PgClientLike` widened with optional
`on`/`off` methods to surface the requirement in the type, with
runtime presence-guards so narrow test doubles remain compatible.
Tests:
- 31 unit tests in errors.test.ts covering all SQLSTATE classes,
all Node connection codes, connection-terminated message
matching, PostgresStoreError instance handling, and edge cases
(null, primitives, non-SQLSTATE codes, non-string codes).
- 3 new integration tests in transactions.test.ts: constraint
violation passthrough (23505 on loop_instances PK via raw
INSERT), classifyError on real pg errors, and mid-tx connection
loss via pg_terminate_backend → TransactionIntegrityError with
kind=transient + cause preserved + indeterminate message.
README gains an "Error classification" section documenting the
classification surface, transient allowlist, and
TransactionIntegrityError semantics.
Surface-Reconciliation-Id: SR-016
…erification
SR-016.6 closes out the Phase A.5 D-12 Postgres production-grade work by
adding an index on the hot `listOpenInstances(loopId)` query path and an
integration test that asserts the planner actually selects it.
- Migration 004 adds a composite B-tree index on
`loop_instances (loop_id, status)`. Chose composite over partial
(`WHERE status = 'active'`) to preserve plan flexibility for any
future LoopStore surface that filters by other statuses; small
storage cost for the flexibility. Uses `CREATE INDEX IF NOT EXISTS`
(not `CONCURRENTLY`) because the migration runner wraps each
migration in a transaction; for RC this is acceptable (new deploys
build against empty tables, existing small deploys tolerate the
brief lock). Migration SQL notes the future non-transactional-
migration stream as the escape hatch for large existing tables.
- `src/__tests__/indexes.test.ts` runs `EXPLAIN (ANALYZE, FORMAT JSON)`
over `listOpenInstances`'s query shape against a realistically-seeded
table (~10k rows across 10 loop_ids × 3 statuses with `ANALYZE`
applied) and asserts two things as first-class, separate checks
per operator guidance at clearance:
1. The plan tree contains a node with
`Index Name === "idx_loop_instances_loop_id_status"`.
2. The plan tree contains no `Seq Scan` node with
`Relation Name === "loop_instances"`.
A third assertion confirms the index actually exists in
`pg_indexes` after `runMigrations`, so a regression in migration
application surfaces as "index missing" rather than "planner chose
seq scan for unclear reasons."
Suite runs against both matrix images (pg 15 and pg 16) to guard
against planner-behavior drift.
- `src/__tests__/migrations.test.ts` updated to include migration 004
in the applied/skipped lists and to track shipped-migration count
dynamically in the advisory-lock test so future migration additions
don't require magic-number updates there.
- `README.md` updated with migration 004 in the shipped-migrations
list and a note on the index's role.
Verification:
- Adapter test suite: 70 passed (70 total) across 6 files.
- `pnpm -C packages/adapters/postgres build` succeeds; C-14
full-stream scan shows only the two pre-existing calibrated
warnings (`.npmrc NODE_AUTH_TOKEN` and tsup `types`-condition
ordering).
- `pnpm -r typecheck` and `pnpm -r build` across the workspace both
pass with no new findings.
Surface-Reconciliation-Id: SR-016
SR-016.7 closes out the seven-sub-commit SR-016 effort that brought
@loop-engine/adapter-postgres to production grade per D-12 → C. This
commit is the rollup: no code changes; documentation, version bump,
and changeset entry only.
Scope:
- DESIGN.md records six load-bearing decisions future PRs should not
reshape without arguing against the recorded rationale:
1. SF-SR016.3-1 + SF-SR016.5-1 shared root cause (pre-existing
latent bugs in uncovered code paths) and the integration-test-
before-publish policy derived from it.
2. statement_timeout wiring via libpq options connection parameter.
3. withTransaction no-op 'error' handler with presence-guarded
client.on / client.off for test-double compatibility.
4. Module split pattern (pool.ts, errors.ts, migrations/runner.ts,
plus buildLoopStoreAgainst factoring in index.ts).
5. Adapter-postgres module structure as candidate family-level
convention (promote when a second production-grade adapter
reaches similar complexity).
6. withTransaction indeterminacy rule: four-way case matrix keyed
on "did the adapter end in a state where the transaction's
terminal outcome is known?", with governing principle "only
wrap in TransactionIntegrityError when terminal state genuinely
unknown."
- package.json version bump 0.1.6 → 0.2.0 (minor; SR-016 is additive
on a 0.x package — existing surface preserved, new surface added).
No in-tree consumers to update (adapter-postgres is a terminal leaf
package; consumers are downstream apps).
- .changeset/1.0.0-rc.0.md appends the SR-016 section with the full
sub-commit sequence, symbol diff against 0.1.6, migration snippets,
out-of-scope list, and verification block.
Phase A.7 verification (adapter scope):
- `pnpm -C packages/adapters/postgres typecheck` → exit 0.
- `pnpm -C packages/adapters/postgres test` → 70/70 passed.
- `pnpm -C packages/adapters/postgres build` → exit 0. C-14 clean
(only pre-existing .npmrc and tsup warnings).
- `npm pack --dry-run` → 56.9 kB packed / 193.8 kB unpacked. Well
under the 100 KB integration-adapter ceiling per the
loop-engine-packaging rule.
Phase A.7 verification (workspace scope):
- `pnpm -r build` → exit 0. C-14 full-stream scan clean.
- `pnpm -r typecheck` → exit 0. C-14 full-stream scan clean.
Companion bd-forge-main commit lands PASS_B_EXECUTION_LOG.md SR-016
aggregate entry, API_SURFACE_SPEC_DRAFT.md R-081 section rewrite,
and the integration-test-before-publish policy in the
loop-engine-packaging rule.
Surface-Reconciliation-Id: SR-016
…ompanion) SR-017 is the D-12 → C companion to SR-016's Postgres production-grade work: Kafka's @experimental portion lands as a typed, JSDoc-tagged stub that throws at call time. Scope: - kafkaEventBus(options) return value gains a `subscribe` method conforming to the EventBus.subscribe? interface shape. The method is @experimental-tagged in JSDoc, has a `: never` return annotation, and throws with a descriptive error. - The `: never` return type is load-bearing. `never` is assignable to `() => void` (the interface's declared return type) as TypeScript's bottom type, so the stub typechecks against the interface while catching callers that bind the return value — `const teardown = bus.subscribe(h)` ends up with `teardown: never`, propagating the mistake to their caller contracts at compile time. - Error message shape per operator refinement at SR-017 clearance: "@loop-engine/adapter-kafka: subscribe() is stubbed at 1.0.0-rc.0. Only emit() is implemented. Track the 1.1.0 milestone for the subscribe() implementation." Consumers who inadvertently call subscribe see the package name, the stub's RC-0 scope, the one stable method (emit), and the milestone their use case blocks on. - kafkaEventBus function gained a JSDoc block documenting the surface-status split (emit stable / subscribe experimental stub). Signature unchanged. - README gained a "Surface status at 1.0.0-rc.0" table describing the two-method state and pointing subscription-needing consumers to adapter-memory's in-memory bus or the future implementation. - Version bump 0.1.6 → 0.1.7 (patch; small additive surface change). Out of scope (intentionally, documented in changeset): - A real subscribe implementation using kafkajs.Consumer: tracked against the 1.1.0 milestone. Each design surface (consumer group management, at-least-once vs at-most-once, offset commit strategy, teardown semantics) is a genuine decision, not a mechanical add. - Integration tests against a real Kafka instance: the integration-test-before-publish policy (landed in SR-016 at bd-forge-main/.cursor/rules/loop-engine-packaging.md §"Pre-publish verification requirements") applies at 1.0.0 promotion; stubs at 0.1.x are grandfathered. Integration coverage expected before adapter-kafka reaches the rc status track. - Dedicated unit tests of the stub throw: the contract is small enough (one method, one thrown error, one known message prefix) that the type system (`: never` return) plus the explicit message provide sufficient verification at RC. Introducing vitest as a dev dep for a one-assertion file is scope-disproportionate for SR-017. Tests land alongside the real implementation in 1.1.0. Verification: - `pnpm -C packages/adapters/kafka typecheck` → exit 0. - `pnpm -C packages/adapters/kafka build` → exit 0. C-14 clean (only pre-existing .npmrc and tsup calibrated warnings). - `pnpm -r typecheck` → exit 0. C-14 clean. - `pnpm -r build` → exit 0. C-14 clean. - `npm pack --dry-run` → 7.4 kB packed / 26.1 kB unpacked. Well under the 100 KB integration-adapter ceiling. Phase A.5 closure: SR-017 closes Phase A.5. The phase's scope (D-12 Postgres + Kafka) is now complete: - Postgres production-grade: SR-016 (7 sub-commits; 2 substantive findings, both pre-existing latent bugs, both resolved in-SR). - Kafka @experimental companion: SR-017 (this commit; 0 findings). Phase A.6 (example trees alignment) opens next. Companion bd-forge-main commit lands PASS_B_EXECUTION_LOG.md SR-017 entry + Phase A.5 close and API_SURFACE_SPEC_DRAFT.md R-078 update. Surface-Reconciliation-Id: SR-017
…ace (SR-018)
Phase A.6 example-tree alignment. Widens typecheck:examples include so all
five ai-actors/shared files are covered (was narrow to loop.ts only, which
masked accumulated pre-reconciliation drift in four files), then fixes the
three compile errors that then surface plus the F-PB-09 orgId cleanup.
Changes
- examples/ai-actors/shared/types.ts
- Remove ReplenishmentContext.orgId (F-PB-09 / D-06).
- Brand loopAggregateId as AggregateId (D-01 / SR-012 idiom).
- examples/ai-actors/shared/scenario.ts
- Drop orgId entry; wrap aggregate-id literal in aggregateId(...) factory.
- examples/ai-actors/shared/actors.ts
- Migrate buildActorEvidence import to buildAIActorEvidence (D-13 post-SR-013b).
- Use actorId(...) factory on agent:demand-forecaster literal (D-01).
- Change buildForecastingActor signature from (agentId, gatewaySessionId)
to (provider, modelId) to match the post-SR-006 AIAgentActor shape.
- Update buildRecommendationEvidence to pass {provider, modelId, ...}
matching buildAIActorEvidence's current signature.
- examples/ai-actors/shared/assertions.ts
- Helper signatures take AggregateId (branded); drop `as never` casts.
- Replace aiTransition.actor.agentId (non-existent field) with reading
modelId/provider from the evidence record.
- Update evidence key references to AIAgentSubmission["evidence"] keys.
- tsconfig.examples.json
- Widen include from loop.ts only to ai-actors/shared/**/*.ts.
F-PB-09 path taken
The prompt flagged a "Tenant interface carrying orgId at types.ts:21".
Actual state: no Tenant type exists; orgId lived on ReplenishmentContext.
Path taken per operator guidance: surgical field removal, no new type
introduced, no type removed (ReplenishmentContext still carries meaningful
scenario state post-cleanup).
Findings
- 0 substantive / 0 procedural / 1 observation (F-PA6-01).
- F-PA6-01: narrow verification coverage in tsconfig.examples.json include
had masked four independent pre-reconciliation idioms across ~17 prior
SRs. Resolved in-SR by widening the include; this is the Phase A.6
analog of SR-015/SR-016's verification-gap root cause.
Verification
- pnpm typecheck:examples → exit 0, all 5 files in scope.
- C-14 full-stream scan clean.
- No package surface change (examples are not packaged).
Phase A.6 closes with this SR. Phase A.7 (end-of-Branch-A verification
pass) opens next.
Surface-Reconciliation-Id: SR-018
Phase A.7 close. Full-gate verification clean: workspace clean-rebuild, typecheck, test (70/70 adapter-postgres integration tests on real Postgres 15+16), typecheck:examples, tarball ceilings (19 packages under ceilings), bd-forge-main split scan (6 F-01 stubs, baseline unchanged), changeset presence. Three observation-tier findings surfaced, all in the operator- predicted spec-regeneration-lag category: - F-PA7-OBS-01: paired-commit trailer discipline adoption gap (bd-forge-main trailers start at SR-010; earlier SRs lack the trailer by historical reality). Documented; no backfill. - F-PA7-OBS-02: AI provider factory-signature spec drift. Anthropic/OpenAI ship (options)->ActorAdapter single-arg; Gemini/Grok ship (apiKey, config?)->ActorAdapter two-arg. Spec regenerated to match shipped dist + cross-adapter shape-divergence note. - F-PA7-OBS-03: loadMigrations signature spec drift (optional dir param, mutable return array). Spec updated. C-15 calibration landed in PASS_B_CALIBRATION_NOTES.md capturing the verification-coverage-gap-as-drift-predictor pattern across A.4 / A.5 / A.6. Branch A is clear for merge. Next: Branch B (loopengine.dev docs), Branch C (loop-examples), Branch D (D-18 package rename). Surface-Reconciliation-Id: SR-019
…positive
`packages/core/src/idFactories.ts` contained a `@example`-style JSDoc
code block with `import { LoopIdSchema } from "@loop-engine/core";`
illustrating where the schema lives. `scripts/check-boundary.ts` uses a
source-agnostic regex that matches `import ... from '...'` without
stripping comments, so it read the docstring as a real self-import of
`@loop-engine/core` and failed the `dependency-declarations` check on
the first CI run of this branch.
Reworded the example to remove the materialized `import` statement while
preserving the "use `*Schema` from this same package" signal:
* If runtime validation is needed (e.g., constraining the format of a
* `LoopId`), use the corresponding `*Schema` from `./schemas` directly
* (exported from this same package):
*
* ```ts
* const id = LoopIdSchema.parse(input); // throws on invalid
* ```
No runtime or type-signature change; docstring-only edit. `pnpm
check-boundary` passes locally (6/6 green).
The underlying tooling hazard (regex false-positives on any JSDoc
`import ... from` example anywhere in the workspace) is logged as
F-PB-18 against a post-Branch-A cleanup; not in scope for this merge.
Surface-Reconciliation-Id: SR-019 (release)
`@changesets/config@3.1.3` validates `ignore` entries against the current workspace and rejects the config when a referenced package does not exist. `@loop-engine/dsl` is reserved for the D-18 rename work in Branch D and has no corresponding workspace package today, so every invocation of `pnpm changeset pre enter` / `pnpm changeset version` fails at the config-read step with: ValidationError: The package or glob expression "@loop-engine/dsl" is specified in the `ignore` option but it is not found in the project. Removing the stale entry unblocks release tooling without touching Branch D scope. When Branch D creates `@loop-engine/dsl`, its prompt will decide whether to re-add the ignore (i.e. whether dsl lives on the main release cycle or a separate track) with real package context in hand. Zero runtime impact; tooling-config hygiene only. Logged as F-PA7-CI-02 (observation-tier · C-15 instance · release-tooling gate surfacing drift on first exercise). Surface-Reconciliation-Id: SR-019 (release)
Split rolling .changeset/1.0.0-rc.0.md into 20 per-SR changeset files (sr-001 through sr-019 with sr-013a/b) and ran `pnpm changeset version` under pre-mode (tag=rc). Recovery from F-PA7-CI-04 + F-PA7-CI-05: - Added @loop-engine/adapter-postgres and @loop-engine/adapter-kafka to .changeset/config.json ignore list. Both stay on their own version tracks (postgres @ 0.2.0 from SR-016; kafka @ 0.1.7 from SR-017). `updateInternalDependencies: patch` no longer cascades a semver-wrong patch bump onto their majored upstreams. - Split rolling changeset into per-SR files. The 94 KB monolithic shape was silently truncated by `changeset version`, losing SR-010 through SR-019 content in per-package CHANGELOGs. Per-SR files are the tool's intended workflow; truncation is resolved and all 20 SRs now appear in every affected package's CHANGELOG. Version outcomes: - 22 public packages bumped to 1.0.0-rc.0 (core, runtime, sdk, actors, guards, loop-definition, events, signals, observability, registry-client, ui-devtools, adapter-memory, adapter-vercel-ai, adapter-perplexity, adapter-anthropic, adapter-openai, adapter-gemini, adapter-grok, adapter-http, adapter-openclaw, adapter-pagerduty, adapter-commerce-gateway). - adapter-postgres unchanged at 0.2.0 (ignore). - adapter-kafka unchanged at 0.1.7 (ignore). - Private apps inspector + playground patch-bumped to 0.1.1-rc.0 (workspace-only tracking of consumed-dep-set change; private: true). Surface-Reconciliation-Id: SR-019 (release)
…st-rename Bundle of F-PA7-CI-06 + F-PA7-CI-07 resolution. Both surfaced on the ce811ae (version-bump) CI run; both block the 1.0.0-rc.0 release path. F-PA7-CI-06 — workspace peerDeps declared via workspace:^ protocol ================================================================== Four packages declared internal peerDeps using literal version ranges (e.g. "@loop-engine/core": "^1.0.0-rc.0"). Before the version bump those literals pointed at ^0.1.5, which resolved against the already-published npm version and worked. After changeset version the literals pointed at ^1.0.0-rc.0 which is not yet published, so `pnpm install --frozen-lockfile` failed in CI with ERR_PNPM_NO_MATCHING_VERSION (chicken-and-egg bootstrap: can't install to build to publish). Switching these specs to workspace:^ makes pnpm resolve against workspace siblings locally; pnpm's publish-transform rewrites workspace:^ to the actual ^X.Y.Z range at publish time, so the published artifact is identical to what the literal range produced. Pure bootstrap-ergonomics fix with zero consumer-visible change. Edited peerDep specs: - packages/adapter-pagerduty @loop-engine/core ^1.0.0-rc.0 -> workspace:^ - packages/adapter-perplexity @loop-engine/core ^1.0.0-rc.0 -> workspace:^ - packages/adapter-vercel-ai @loop-engine/core ^1.0.0-rc.0 -> workspace:^ - packages/runtime @loop-engine/events ^1.0.0-rc.0 -> workspace:^ F-PA7-CI-07 — adapter-vercel-ai aligned to post-SR-010 schema field names ========================================================================= The workspace:^ conversion above unmasked pre-existing drift in packages/adapter-vercel-ai/src/loop-tool-bridge.ts. That file referenced pre-reconciliation field names (definition.loopId, transition.transitionId) which were renamed to definition.id / transition.id during SR-004 + SR-010. The pre-fix literal peerDep resolved @loop-engine/core against externally- published 0.1.5 (which still carried the old field shape), so typecheck falsely passed through all of Pass B against stale input types. Five-token mechanical rename, identical in kind to SR-018's example-tree alignment: - definition.loopId -> definition.id (3 instances) - transition.transitionId -> transition.id (3 instances — 2 wrapped in String()) No behavioral change, no signature change, no new imports. Engine API keyword arguments (loopId:, transitionId:) in the call-site argument positions stay unchanged — those are the engine's public input shape; only the right-hand-side schema-field reads moved. Scope verification ------------------ - `pnpm -r typecheck` clean across all 27 workspace projects (confirms the drift is isolated to this one file; every other package already typechecked against workspace types via workspace:* deps). - `pnpm -r build` clean. - `pnpm -r test` clean (no tests touch the edited lines). - pnpm-lock.yaml regenerated; adapter-postgres (0.2.0) and adapter-kafka (0.1.7) still on their separate version tracks. Classification -------------- F-PA7-CI-06: observation-tier C-15 instance (eighth; verification-gate coverage widened on release-path exercise). F-PA7-CI-07: substantive-tier C-15 *variant* (ninth; verification-against- stale-inputs rather than verification-gap — the typecheck gate existed but its input types were drifted. Distinct from prior C-15 instances. SR-018's "example trees" framing should structurally have been "all workspace files referencing reconciled field names" — to be logged as a scope-derivation lesson for future reconciliation work). Surface-Reconciliation-Id: SR-018 (completion) + SR-019 (release)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Branch A of the OSS repackaging / API surface reconciliation work. This PR materializes the complete surface reconciliation for
1.0.0-rc.0across the@loop-engine/*workspace.Nineteen Surface-Reconciliation (SR) commits land here, spanning SR-001 through SR-019, preserving per-SR attribution via
Surface-Reconciliation-Id:commit trailers. Merge-commit, not squash — the SR chain is load-bearing audit history.Do not squash. Branch protection / repo default must be overridden for this PR.
Phase rollup
Final gate results (SR-019, re-run pre-push)
pnpm -r build— cleanpnpm -r typecheck— cleanpnpm typecheck:examples— cleanpnpm -r test— 224 tests passed across 18 packages, 0 failures (incl. adapter-postgres 70/70 on testcontainers Postgres).changeset/1.0.0-rc.0.md— present (94 KB changelog input)Findings (all observation-tier, documented, non-blocking)
examples/ai-actors/shared/surfaced by wideningtsconfig.examples.jsoninclude. Resolved in SR-018.(options)for Anthropic/OpenAI;(apiKey, config?)for Gemini/Grok). Spec regenerated to reflect shipped surface; uniformity filed as F-PB-17 for1.1.0.loadMigrations(dir?: string): Promise<Migration[]>signature; spec updated to match shipped shape.Post-merge release path
pnpm changeset versiononmainpost-merge — materialize.changeset/1.0.0-rc.0.mdinto per-packagepackage.json+CHANGELOG.mdupdates.v1.0.0-rc.0on the post-version-bump HEAD; push tag.rc-tag-release.yml, which publishes@loop-engine/*packages to npm with OIDC trusted-publishing + provenance.Paired repo
loopengine.dev/docs/1.0.0-rc.0-source-reconciliationcarries the paired docs updates (SR-001/002/003 co-commits + subsequent docs alignments). Merges alongside this PR.Non-goals
Branches B/C/D (docs alignment, examples alignment, D-18 package rename) are out of scope for this merge.
Merge instructions
loopengine.devPR alongside / after this one.