@north-light/crouter 0.3.227 → 0.3.228
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +5 -5
- package/dist/api/client.js +3 -3
- package/dist/api/dto/broker-ops.d.ts +8 -0
- package/dist/api/dto/memory.d.ts +8 -7
- package/dist/api/dto/memory.js +2 -2
- package/dist/builtin-memory/05-kinds/design/00-base.md +3 -4
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +4 -3
- package/dist/builtin-memory/05-kinds/design/design-contract.md +3 -6
- package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -4
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +4 -5
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +5 -13
- package/dist/builtin-memory/design/guide.md +38 -16
- package/dist/builtin-memory/design/roadmap.md +4 -4
- package/dist/builtin-memory/insights/init.md +5 -5
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +6 -6
- package/dist/builtin-memory/internal/plugins.md +1 -1
- package/dist/builtin-memory/plan/guide.md +53 -0
- package/dist/builtin-memory/plan/roadmap.md +10 -8
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +50 -37
- package/dist/clients/attach/__tests__/pane-tag-successor.test.js +56 -0
- package/dist/clients/attach/__tests__/profile-files.test.js +21 -0
- package/dist/clients/attach/chrome/inbox-strip.js +11 -7
- package/dist/clients/attach/session/pane-tag.d.ts +28 -2
- package/dist/clients/attach/session/pane-tag.js +48 -22
- package/dist/clients/attach/session/profile-files.d.ts +6 -0
- package/dist/clients/attach/session/profile-files.js +44 -11
- package/dist/clients/attach/slash/dispatch.js +4 -7
- package/dist/clients/attach/viewer.js +588 -596
- package/dist/commands/__tests__/surface-reload-target.test.js +21 -0
- package/dist/commands/api-client.d.ts +3 -7
- package/dist/commands/api-client.js +7 -12
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +292 -0
- package/dist/commands/memory/__tests__/repository-root-lint.test.js +146 -0
- package/dist/commands/memory/delete.js +51 -24
- package/dist/commands/memory/edit.js +50 -10
- package/dist/commands/memory/find.js +105 -56
- package/dist/commands/memory/history.js +51 -31
- package/dist/commands/memory/lint.d.ts +0 -9
- package/dist/commands/memory/lint.js +78 -322
- package/dist/commands/memory/list.d.ts +12 -4
- package/dist/commands/memory/list.js +64 -25
- package/dist/commands/memory/move.js +103 -73
- package/dist/commands/memory/origin.js +35 -5
- package/dist/commands/memory/read.d.ts +4 -0
- package/dist/commands/memory/read.js +132 -80
- package/dist/commands/memory/shared.d.ts +115 -25
- package/dist/commands/memory/shared.js +331 -74
- package/dist/commands/memory/write.js +72 -21
- package/dist/commands/memory.js +2 -2
- package/dist/commands/node/lifecycle.js +2 -2
- package/dist/commands/pkg/market-manage.js +25 -24
- package/dist/commands/pkg/plugin-manage.d.ts +22 -6
- package/dist/commands/pkg/plugin-manage.js +118 -25
- package/dist/commands/pkg/shared.d.ts +8 -0
- package/dist/commands/pkg/shared.js +23 -20
- package/dist/commands/surface-reload.d.ts +5 -0
- package/dist/commands/surface-reload.js +9 -1
- package/dist/commands/sys/__tests__/migrate.test.js +1140 -22
- package/dist/commands/sys/__tests__/sync-project-guidance.test.js +218 -0
- package/dist/commands/sys/migrate.js +126 -140
- package/dist/commands/sys/panels/profiles-panel.d.ts +66 -0
- package/dist/commands/sys/panels/profiles-panel.js +599 -0
- package/dist/commands/sys/settings-shell.d.ts +4 -2
- package/dist/commands/sys/settings-shell.js +35 -4
- package/dist/commands/sys/settings.js +21 -5
- package/dist/commands/sys/sync-deps.d.ts +2 -0
- package/dist/commands/sys/sync-deps.js +26 -15
- package/dist/commands/sys/sync-project-guidance.js +228 -145
- package/dist/commands/sys/sync-skills.js +28 -17
- package/dist/commands/sys/update.js +11 -3
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +15 -2
- package/dist/core/__tests__/config-change-delta.test.js +106 -0
- package/dist/core/__tests__/context-intro.test.js +13 -9
- package/dist/core/__tests__/daemon-boot.test.js +22 -16
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +5 -1
- package/dist/core/__tests__/human-deliver.test.js +19 -0
- package/dist/core/__tests__/inline-memory-refs.test.js +50 -46
- package/dist/core/__tests__/{serial → integration}/broker-fork-seam.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/broker-sdk-wiring.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/broker-snapshot-history.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/command-plugins.test.js +94 -3
- package/dist/core/__tests__/{serial → integration}/deferred-no-wake.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/flagship-lifecycle.test.js +13 -27
- package/dist/core/__tests__/{serial → integration}/host-teardown-process-group.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/human-deliver-e2e.test.js +1 -1
- package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js} +22 -33
- package/dist/core/__tests__/{serial → integration}/live-mutation.test.js +21 -41
- package/dist/core/__tests__/integration/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/refresh-stall-recycle.test.js +1 -1
- package/dist/core/__tests__/integration/revive.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/revive.test.js +38 -19
- package/dist/core/__tests__/integration/spawn-root.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/spawn-root.test.js +104 -1
- package/dist/core/__tests__/integration/subscription-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/subscription-delivery.test.js +1 -1
- package/dist/core/__tests__/integration/tmux-surface.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/tmux-surface.test.js +1 -1
- package/dist/core/__tests__/integration/worktree-land.test.d.ts +1 -0
- package/dist/core/__tests__/integration/worktree-land.test.js +400 -0
- package/dist/core/__tests__/integration/worktree-reap.test.d.ts +1 -0
- package/dist/core/__tests__/{serial/worktree.test.js → integration/worktree-reap.test.js} +6 -338
- package/dist/core/__tests__/kickoff.test.js +16 -5
- package/dist/core/__tests__/memory-resolver-precedence.test.js +122 -91
- package/dist/core/__tests__/nested-store-discovery.test.js +5 -3
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +39 -27
- package/dist/core/__tests__/on-read-nested-store.test.js +19 -13
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +138 -44
- package/dist/core/__tests__/repository-association.test.d.ts +1 -0
- package/dist/core/__tests__/repository-association.test.js +153 -0
- package/dist/core/__tests__/repository-root-identity.test.d.ts +1 -0
- package/dist/core/__tests__/repository-root-identity.test.js +219 -0
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +27 -11
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.d.ts +1 -0
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +40 -0
- package/dist/core/canvas/canvas.d.ts +3 -0
- package/dist/core/canvas/canvas.js +30 -10
- package/dist/core/canvas/db.js +28 -1
- package/dist/core/canvas/paths.d.ts +4 -6
- package/dist/core/canvas/paths.js +8 -6
- package/dist/core/canvas/render-source.js +2 -2
- package/dist/core/canvas/types.d.ts +9 -13
- package/dist/core/exclusive-lock.d.ts +2 -0
- package/dist/core/exclusive-lock.js +21 -0
- package/dist/core/git.d.ts +0 -1
- package/dist/core/git.js +0 -3
- package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +1 -0
- package/dist/core/human/feedback-companion.js +3 -0
- package/dist/core/human/scan.js +8 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +2 -1
- package/dist/core/memory/doc-link-grammar.js +2 -2
- package/dist/core/memory/history.d.ts +24 -0
- package/dist/core/memory/history.js +66 -1
- package/dist/core/memory/identity.d.ts +65 -0
- package/dist/core/memory/identity.js +185 -0
- package/dist/core/memory/inline-ref-guidance.d.ts +1 -1
- package/dist/core/memory/inline-ref-guidance.js +1 -1
- package/dist/core/memory/inline-ref-inventory.d.ts +3 -10
- package/dist/core/memory/inline-ref-inventory.js +30 -62
- package/dist/core/memory/lint.d.ts +129 -0
- package/dist/core/memory/lint.js +517 -0
- package/dist/core/memory/project-namespace.d.ts +31 -0
- package/dist/core/memory/project-namespace.js +85 -0
- package/dist/core/memory/repository-association.d.ts +31 -0
- package/dist/core/memory/repository-association.js +129 -0
- package/dist/core/memory/tree.d.ts +39 -0
- package/dist/core/memory/tree.js +93 -0
- package/dist/core/memory-resolver.d.ts +193 -90
- package/dist/core/memory-resolver.js +461 -375
- package/dist/core/nested-stores.js +6 -13
- package/dist/core/profiles/select.d.ts +4 -1
- package/dist/core/profiles/select.js +108 -48
- package/dist/core/review/__tests__/capture-origin.test.js +2 -2
- package/dist/core/review/__tests__/stage-identity.test.js +2 -2
- package/dist/core/review/companion.js +11 -2
- package/dist/core/runtime/bearings.d.ts +2 -2
- package/dist/core/runtime/bearings.js +3 -3
- package/dist/core/runtime/broker/daemon-ops.d.ts +2 -2
- package/dist/core/runtime/broker/rebind.js +5 -0
- package/dist/core/runtime/broker-extension-render.d.ts +7 -3
- package/dist/core/runtime/broker-extension-render.js +10 -5
- package/dist/core/runtime/broker-persona-guidance.d.ts +19 -5
- package/dist/core/runtime/broker-persona-guidance.js +122 -27
- package/dist/core/runtime/deliver-live.d.ts +16 -4
- package/dist/core/runtime/deliver-live.js +29 -15
- package/dist/core/runtime/kickoff.d.ts +3 -3
- package/dist/core/runtime/kickoff.js +8 -16
- package/dist/core/runtime/lifecycle.js +2 -3
- package/dist/core/runtime/nodes.d.ts +3 -4
- package/dist/core/runtime/nodes.js +3 -4
- package/dist/core/runtime/persona.d.ts +8 -12
- package/dist/core/runtime/persona.js +24 -96
- package/dist/core/runtime/promote.d.ts +2 -2
- package/dist/core/runtime/promote.js +8 -21
- package/dist/core/runtime/revive.js +20 -17
- package/dist/core/runtime/spawn.js +18 -7
- package/dist/core/runtime/tmux-bindings.js +2 -3
- package/dist/core/runtime/warm-pool.js +3 -4
- package/dist/core/scope.js +2 -0
- package/dist/core/self-update.d.ts +0 -2
- package/dist/core/self-update.js +2 -35
- package/dist/core/substrate/__tests__/surface-match-memory-read.test.d.ts +1 -0
- package/dist/core/substrate/__tests__/surface-match-memory-read.test.js +28 -0
- package/dist/core/substrate/index.d.ts +2 -2
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.d.ts +43 -27
- package/dist/core/substrate/injected-store.js +208 -104
- package/dist/core/substrate/listings.d.ts +19 -12
- package/dist/core/substrate/listings.js +75 -52
- package/dist/core/substrate/on-read-node.d.ts +4 -7
- package/dist/core/substrate/on-read-node.js +6 -8
- package/dist/core/substrate/on-read.d.ts +22 -25
- package/dist/core/substrate/on-read.js +103 -147
- package/dist/core/substrate/render-node.d.ts +4 -7
- package/dist/core/substrate/render-node.js +5 -7
- package/dist/core/substrate/render.d.ts +21 -3
- package/dist/core/substrate/render.js +291 -223
- package/dist/core/substrate/schema.d.ts +1 -13
- package/dist/core/substrate/schema.js +5 -40
- package/dist/core/substrate/session-cache.d.ts +14 -4
- package/dist/core/substrate/session-cache.js +40 -22
- package/dist/core/substrate/surface-match.d.ts +9 -7
- package/dist/core/substrate/surface-match.js +26 -25
- package/dist/daemon/__tests__/helpers/source-daemon.d.ts +30 -0
- package/dist/daemon/__tests__/helpers/source-daemon.js +174 -0
- package/dist/daemon/__tests__/integration/migration-startup.test.d.ts +1 -0
- package/dist/daemon/__tests__/integration/migration-startup.test.js +97 -0
- package/dist/daemon/api/__tests__/bridge-heartbeat.test.js +21 -5
- package/dist/daemon/api/handlers/broker-ops.js +21 -16
- package/dist/daemon/api/handlers/memory.js +2 -0
- package/dist/daemon/crtrd.js +2 -0
- package/dist/daemon/human/finish.js +8 -1
- package/dist/daemon/manage.d.ts +0 -1
- package/dist/daemon/manage.js +7 -16
- package/dist/daemon/startup-policy.d.ts +1 -0
- package/dist/daemon/startup-policy.js +1 -0
- package/dist/migrations/001-surfaces-frontmatter.js +21 -109
- package/dist/migrations/002-profile-project-memory.js +1 -0
- package/dist/migrations/003-repository-root-memory-identity/front-door.d.ts +26 -0
- package/dist/migrations/003-repository-root-memory-identity/front-door.js +231 -0
- package/dist/migrations/003-repository-root-memory-identity/index.d.ts +2 -0
- package/dist/migrations/003-repository-root-memory-identity/index.js +513 -0
- package/dist/migrations/003-repository-root-memory-identity/references.d.ts +95 -0
- package/dist/migrations/003-repository-root-memory-identity/references.js +469 -0
- package/dist/migrations/003-repository-root-memory-identity/repository-facts.d.ts +39 -0
- package/dist/migrations/003-repository-root-memory-identity/repository-facts.js +349 -0
- package/dist/migrations/__tests__/activation-concurrency.test.d.ts +1 -0
- package/dist/migrations/__tests__/activation-concurrency.test.js +145 -0
- package/dist/migrations/__tests__/activation.test.d.ts +1 -0
- package/dist/migrations/__tests__/activation.test.js +149 -0
- package/dist/migrations/__tests__/deletion-and-root-declaration.test.d.ts +1 -0
- package/dist/migrations/__tests__/deletion-and-root-declaration.test.js +149 -0
- package/dist/migrations/activation.d.ts +16 -0
- package/dist/migrations/activation.js +78 -0
- package/dist/migrations/convergent.d.ts +14 -3
- package/dist/migrations/convergent.js +21 -10
- package/dist/migrations/corpus.d.ts +78 -0
- package/dist/migrations/corpus.js +497 -0
- package/dist/migrations/frontmatter-splice.d.ts +15 -0
- package/dist/migrations/frontmatter-splice.js +176 -0
- package/dist/migrations/registry.d.ts +6 -1
- package/dist/migrations/registry.js +7 -2
- package/dist/migrations/runner.d.ts +41 -0
- package/dist/migrations/runner.js +81 -0
- package/dist/migrations/types.d.ts +148 -9
- package/dist/migrations/types.js +9 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +225 -17
- package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +11 -3
- package/dist/pi-extensions/canvas-context-intro.d.ts +3 -5
- package/dist/pi-extensions/canvas-context-intro.js +50 -46
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +1 -8
- package/dist/pi-extensions/canvas-doc-substrate.js +55 -122
- package/dist/pi-extensions/canvas-stophook.js +6 -13
- package/dist/shared/generated-context.d.ts +0 -3
- package/dist/shared/generated-context.js +0 -57
- package/dist/shared/tool-groups.js +2 -3
- package/package.json +5 -4
- package/runtime.lock.json +2 -2
- /package/dist/api/__tests__/{serial → integration}/client.test.d.ts +0 -0
- /package/dist/api/__tests__/{serial → integration}/client.test.js +0 -0
- /package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{serial → integration}/provider-rotation.test.ts +0 -0
- /package/dist/clients/{inbox/__tests__/serial/inbox-controller.test.d.ts → attach/__tests__/pane-tag-successor.test.d.ts} +0 -0
- /package/dist/clients/{inbox/__tests__/serial/mount-panel.test.d.ts → attach/__tests__/profile-files.test.d.ts} +0 -0
- /package/dist/{core/__tests__/live-mutation-verbs.test.d.ts → clients/inbox/__tests__/integration/inbox-controller.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{serial → integration}/inbox-controller.test.js +0 -0
- /package/dist/{core/__tests__/serial/broker-fork-seam.test.d.ts → clients/inbox/__tests__/integration/mount-panel.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{serial → integration}/mount-panel.test.js +0 -0
- /package/dist/{core/__tests__/serial/broker-sdk-wiring.test.d.ts → commands/__tests__/surface-reload-target.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/broker-snapshot-history.test.d.ts → commands/memory/__tests__/command-selector-and-mutation-guards.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/command-plugins.test.d.ts → commands/memory/__tests__/repository-root-lint.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/deferred-no-wake.test.d.ts → commands/sys/__tests__/sync-project-guidance.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/flagship-lifecycle.test.d.ts → config-change-delta.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/host-teardown-process-group.test.d.ts → integration/broker-fork-seam.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/human-deliver-e2e.test.d.ts → integration/broker-sdk-wiring.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/live-mutation.test.d.ts → integration/broker-snapshot-history.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/refresh-stall-recycle.test.d.ts → integration/command-plugins.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/revive.test.d.ts → integration/deferred-no-wake.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/spawn-root.test.d.ts → integration/flagship-lifecycle.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/subscription-delivery.test.d.ts → integration/host-teardown-process-group.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/tmux-surface.test.d.ts → integration/human-deliver-e2e.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/worktree.test.d.ts → integration/live-mutation-verbs.test.d.ts} +0 -0
- /package/dist/core/{human/__tests__/serial/inbox-core.test.d.ts → __tests__/integration/live-mutation.test.d.ts} +0 -0
- /package/dist/core/human/__tests__/{serial → integration}/inbox-core.test.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Contract-seam tests for the external command-plugin seam (Phase 1).
|
|
2
|
-
// Run with: npm run test:
|
|
2
|
+
// Run with: npm run test:integration
|
|
3
3
|
//
|
|
4
4
|
// All tests run against an ISOLATED home (HOME → a fresh temp dir, no ambient
|
|
5
5
|
// user plugins) and pass an explicit no-`.crouter` startDir, so discovery is
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// mount composition without executing the fixture binary.
|
|
24
24
|
import { test, describe, before, after, beforeEach, afterEach } from 'node:test';
|
|
25
25
|
import assert from 'node:assert/strict';
|
|
26
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, chmodSync, existsSync } from 'node:fs';
|
|
26
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, chmodSync, existsSync, lstatSync, realpathSync } from 'node:fs';
|
|
27
27
|
import { readFileSync } from 'node:fs';
|
|
28
28
|
import { execFileSync } from 'node:child_process';
|
|
29
29
|
import { tmpdir } from 'node:os';
|
|
@@ -40,6 +40,7 @@ import { CrtrError } from '../../errors.js';
|
|
|
40
40
|
import { resetScopeCache } from '../../scope.js';
|
|
41
41
|
import { listInstalledPluginsInRoot } from '../../resolver.js';
|
|
42
42
|
import { pluginShow } from '../../../commands/pkg/plugin-inspect.js';
|
|
43
|
+
import { pluginDisable, pluginEnable, pluginInstall } from '../../../commands/pkg/plugin-manage.js';
|
|
43
44
|
// The set of reserved core names discovery must fail closed against. We don't
|
|
44
45
|
// import the real SUBTREE_NAMES (build-root.ts pulls the whole tree); a small
|
|
45
46
|
// explicit set is enough for the collision contract and keeps the test cheap.
|
|
@@ -371,7 +372,7 @@ describe('protocol integration', () => {
|
|
|
371
372
|
// 4b. True end-to-end CLI invocation (argv → parseArgv → adapter → renderer)
|
|
372
373
|
// ---------------------------------------------------------------------------
|
|
373
374
|
describe('end-to-end CLI invocation', () => {
|
|
374
|
-
// dist/cli.js is up 4 from src/core/__tests__/
|
|
375
|
+
// dist/cli.js is up 4 from src/core/__tests__/integration/. Requires a prior
|
|
375
376
|
// `npm run build` (the harness/CI builds before tests). Drives the WHOLE seam: the
|
|
376
377
|
// declared positional `app-id` must arrive as `appId` in the request, and
|
|
377
378
|
// the rendered + --json outputs come through the real dispatcher.
|
|
@@ -663,3 +664,93 @@ process.stdout.write(JSON.stringify({ protocolVersion: 1, ok: true, result: { ap
|
|
|
663
664
|
assertNotExecuted();
|
|
664
665
|
});
|
|
665
666
|
});
|
|
667
|
+
// ---------------------------------------------------------------------------
|
|
668
|
+
// Memory-corpus gate across the plugin lifecycle — install, and enable of a
|
|
669
|
+
// package installed while disabled, both converge the candidate's memory store
|
|
670
|
+
// onto exact canonical identity BEFORE it can mount. A local-path install stays
|
|
671
|
+
// a live link, so the caller's own directory is the corpus that gets rewritten;
|
|
672
|
+
// a corpus that cannot converge writes nothing and changes no config. Update
|
|
673
|
+
// runs the same gate through the same helper, so it needs no cases of its own.
|
|
674
|
+
// ---------------------------------------------------------------------------
|
|
675
|
+
describe('memory corpus gate across the plugin lifecycle', () => {
|
|
676
|
+
let realCwd;
|
|
677
|
+
beforeEach(() => {
|
|
678
|
+
realCwd = process.cwd();
|
|
679
|
+
process.chdir(emptyStart); // no .crouter ancestor → the real repo is never a scope
|
|
680
|
+
resetScopeCache();
|
|
681
|
+
});
|
|
682
|
+
afterEach(() => {
|
|
683
|
+
process.chdir(realCwd);
|
|
684
|
+
resetScopeCache();
|
|
685
|
+
});
|
|
686
|
+
function writeDoc(path, frontmatter, body) {
|
|
687
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
688
|
+
writeFileSync(path, `---\n${frontmatter}\nwhen-and-why-to-read: When the fixture runs, this knowledge should be read because it proves the gate.\n---\n\n${body}\n`);
|
|
689
|
+
}
|
|
690
|
+
function pluginSource(name) {
|
|
691
|
+
const root = join(mintDir('crtr-plugin-src-'), name);
|
|
692
|
+
mkdirSync(join(root, '.crouter-plugin'), { recursive: true });
|
|
693
|
+
writeFileSync(join(root, '.crouter-plugin', 'plugin.json'), JSON.stringify({ name, version: '0.1.0', description: 'fixture' }));
|
|
694
|
+
return root;
|
|
695
|
+
}
|
|
696
|
+
function userConfig() {
|
|
697
|
+
const path = join(userRoot, 'config.json');
|
|
698
|
+
return existsSync(path) ? JSON.parse(readFileSync(path, 'utf8')) : { plugins: {} };
|
|
699
|
+
}
|
|
700
|
+
test('a local install migrates the caller-owned linked corpus in place and reports it', async () => {
|
|
701
|
+
const source = pluginSource('memfix');
|
|
702
|
+
writeDoc(join(source, 'memory', 'topics', 'INDEX.md'), 'kind: knowledge\nname: topics/INDEX', 'Topic overview.');
|
|
703
|
+
writeDoc(join(source, 'memory', 'overview.md'), 'kind: knowledge', 'Depth lives in [[memfix/topics/INDEX]].');
|
|
704
|
+
// Non-document files the substrate never loads as documents: the count
|
|
705
|
+
// below must not include them even though lint YAML-checks them too.
|
|
706
|
+
writeDoc(join(source, 'memory', 'SKILL.md'), 'kind: knowledge\nname: unrelated-skill', 'Skill body.');
|
|
707
|
+
writeDoc(join(source, 'memory', 'MEMORY.md'), 'kind: knowledge', 'Index body.');
|
|
708
|
+
resetScopeCache();
|
|
709
|
+
const out = await pluginInstall.run({ installRef: source, scope: 'user' });
|
|
710
|
+
const memory = out['memory'];
|
|
711
|
+
assert.deepEqual(memory.migrated, ['overview.md', 'topics/INDEX.md'], 'both phases rewrote a document');
|
|
712
|
+
assert.equal(memory.linkedSource, realpathSync(join(source, 'memory')), 'the report names the caller-owned directory it rewrote');
|
|
713
|
+
assert.equal(memory.docs, 2);
|
|
714
|
+
assert.equal(lstatSync(join(userRoot, 'plugins', 'memfix')).isSymbolicLink(), true, 'the install stays a live link');
|
|
715
|
+
// The rewrite landed in the caller's source, not in a copy.
|
|
716
|
+
assert.match(readFileSync(join(source, 'memory', 'overview.md'), 'utf8'), /\[\[memfix\/topics\]\]/);
|
|
717
|
+
assert.doesNotMatch(readFileSync(join(source, 'memory', 'topics', 'INDEX.md'), 'utf8'), /^name:/m);
|
|
718
|
+
assert.equal(userConfig().plugins['memfix']?.enabled, true);
|
|
719
|
+
});
|
|
720
|
+
test('a corpus that cannot converge blocks the install, writes nothing, and registers nothing', async () => {
|
|
721
|
+
const source = pluginSource('blocked');
|
|
722
|
+
writeDoc(join(source, 'memory', 'dup-a.md'), 'kind: knowledge\nname: dup', 'First claimant.');
|
|
723
|
+
writeDoc(join(source, 'memory', 'dup-b.md'), 'kind: knowledge\nname: dup', 'Second claimant.');
|
|
724
|
+
const before = ['dup-a.md', 'dup-b.md'].map((f) => readFileSync(join(source, 'memory', f), 'utf8'));
|
|
725
|
+
resetScopeCache();
|
|
726
|
+
await assert.rejects(pluginInstall.run({ installRef: source, scope: 'user' }), (error) => /cannot be migrated to exact canonical identity/.test(error.message) && /dup/.test(error.message));
|
|
727
|
+
assert.equal(existsSync(join(userRoot, 'plugins', 'blocked')), false, 'a blocked candidate installs nothing');
|
|
728
|
+
assert.equal(userConfig().plugins['blocked'], undefined, 'config is unchanged');
|
|
729
|
+
assert.deepEqual(['dup-a.md', 'dup-b.md'].map((f) => readFileSync(join(source, 'memory', f), 'utf8')), before, 'a blocked plan writes nothing');
|
|
730
|
+
});
|
|
731
|
+
test('enable migrates a package installed while disabled before it flips config', async () => {
|
|
732
|
+
const source = pluginSource('lifecycle');
|
|
733
|
+
writeDoc(join(source, 'memory', 'ready.md'), 'kind: knowledge', 'Already canonical.');
|
|
734
|
+
resetScopeCache();
|
|
735
|
+
await pluginInstall.run({ installRef: source, scope: 'user' });
|
|
736
|
+
resetScopeCache();
|
|
737
|
+
await pluginDisable.run({ name: 'lifecycle' });
|
|
738
|
+
resetScopeCache();
|
|
739
|
+
// The package gains an old-format document while it sits disabled.
|
|
740
|
+
writeDoc(join(source, 'memory', 'guide', 'INDEX.md'), 'kind: knowledge\nname: guide/INDEX', 'Guide.');
|
|
741
|
+
const enabled = await pluginEnable.run({ name: 'lifecycle' });
|
|
742
|
+
assert.equal(enabled['enabled'], true);
|
|
743
|
+
const memory = enabled['memory'];
|
|
744
|
+
assert.deepEqual(memory.migrated, ['guide/INDEX.md']);
|
|
745
|
+
assert.equal(memory.linkedSource, realpathSync(join(source, 'memory')));
|
|
746
|
+
assert.doesNotMatch(readFileSync(join(source, 'memory', 'guide', 'INDEX.md'), 'utf8'), /^name:/m);
|
|
747
|
+
assert.equal(userConfig().plugins['lifecycle']?.enabled, true);
|
|
748
|
+
resetScopeCache();
|
|
749
|
+
await pluginDisable.run({ name: 'lifecycle' });
|
|
750
|
+
resetScopeCache();
|
|
751
|
+
writeDoc(join(source, 'memory', 'dup-a.md'), 'kind: knowledge\nname: dup', 'First claimant.');
|
|
752
|
+
writeDoc(join(source, 'memory', 'dup-b.md'), 'kind: knowledge\nname: dup', 'Second claimant.');
|
|
753
|
+
await assert.rejects(pluginEnable.run({ name: 'lifecycle' }), /cannot be migrated to exact canonical identity/);
|
|
754
|
+
assert.equal(userConfig().plugins['lifecycle']?.enabled, false, 'a refused corpus leaves the plugin disabled');
|
|
755
|
+
});
|
|
756
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Run with: npm run test:
|
|
1
|
+
// Run with: npm run test:integration
|
|
2
2
|
//
|
|
3
3
|
// HEADLESS RETARGET (foundation-spec §C.9 + §E). The flagship end-to-end
|
|
4
4
|
// lifecycle scenario, driven entirely at the MODEL layer against FABRICATED
|
|
@@ -29,20 +29,11 @@
|
|
|
29
29
|
// "B revived" asserts go RED. If a push wrongly fanned past its direct
|
|
30
30
|
// subscriber, the "A inbox empty" one-hop asserts go RED.
|
|
31
31
|
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
// personaDrift) and asserts the downstream lifecycle/daemon CONSEQUENCE, leaving
|
|
38
|
-
// the hook-commit proof to the already-pure siblings:
|
|
39
|
-
// • idle-release commit at agent_end → canvas-stophook-agentend.test.ts
|
|
40
|
-
// ('natural stop while awaiting a live worker → idle-release …').
|
|
41
|
-
// • stall reprompt at agent_end → canvas-stophook-agentend.test.ts
|
|
42
|
-
// ('stalled leaf … is still reprompted').
|
|
43
|
-
// • persona-drift steer + ack commit at turn_end → persona.test.ts
|
|
44
|
-
// ('personaDrift detects base→orchestrator after promote, then clears on
|
|
45
|
-
// commit'). Here we assert only the PENDING drift the injector would deliver.
|
|
32
|
+
// HOST-HOOK CAVEAT — fabrication cannot run agent_end or the broker's
|
|
33
|
+
// pre-provider persona gate. This test enacts or asserts model state only;
|
|
34
|
+
// canvas-stophook-agentend.test.ts pins idle release and stall reprompt, while
|
|
35
|
+
// canvas-context-intro.test.ts pins persona transition delivery and ack. Here,
|
|
36
|
+
// personaDrift proves the promoted node remains pending.
|
|
46
37
|
//
|
|
47
38
|
// 2×2 coverage (mode {base,orchestrator} × lifecycle {terminal,resident}):
|
|
48
39
|
// • base×resident — A's fabricated row shape at S1 (a spawnRoot
|
|
@@ -114,7 +105,7 @@ test('flagship: full node lifecycle — spawn, dormancy, one-hop wake, promote,
|
|
|
114
105
|
status: 'active', pi_pid: deadPid(), pi_session_id: 'sess-B',
|
|
115
106
|
});
|
|
116
107
|
subscribe(A, B, true); // the load-bearing spine seed: A subscribes_to B (active).
|
|
117
|
-
commitPersonaAck(B, { mode: 'base', lifecycle: 'terminal' }); // born acked to its persona
|
|
108
|
+
commitPersonaAck(B, { kind: 'developer', mode: 'base', lifecycle: 'terminal' }); // born acked to its persona
|
|
118
109
|
{
|
|
119
110
|
const b = h.node(B);
|
|
120
111
|
assert.equal(b.host_kind, 'broker', 'B broker-hosted (paneless)');
|
|
@@ -211,20 +202,15 @@ test('flagship: full node lifecycle — spawn, dormancy, one-hop wake, promote,
|
|
|
211
202
|
assert.equal(b.intent ?? null, null, 'B intent untouched by promote');
|
|
212
203
|
}
|
|
213
204
|
// ===================================================================
|
|
214
|
-
// S9 — The next
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
// injector delivers). The actual delivery + ack commit is locked
|
|
218
|
-
// by persona.test.ts.
|
|
219
|
-
// (b) agent_end would stall (orchestrator, no live sub — C done, D not
|
|
220
|
-
// yet spawned — no final) → evaluateStop → 'stalled'/STALL_REPROMPT.
|
|
221
|
-
// The actual reprompt injection is locked by canvas-stophook-agentend.
|
|
205
|
+
// S9 — The next provider request must first deliver the pending persona
|
|
206
|
+
// transition. Independently, agent_end would stall this orchestrator
|
|
207
|
+
// because it has no live subscription and has not pushed a final.
|
|
222
208
|
// ===================================================================
|
|
223
209
|
{
|
|
224
210
|
const drift = personaDrift(B);
|
|
225
|
-
assert.ok(drift !== null, '
|
|
226
|
-
assert.deepEqual(drift?.from, { mode: 'base', lifecycle: 'terminal' }, 'drift.from = the acked base×terminal');
|
|
227
|
-
assert.deepEqual(drift?.to, { mode: 'orchestrator', lifecycle: 'terminal' }, 'drift.to = the new orchestrator persona');
|
|
211
|
+
assert.ok(drift !== null, 'the provider gate has a pending persona transition');
|
|
212
|
+
assert.deepEqual(drift?.from, { kind: 'developer', mode: 'base', lifecycle: 'terminal' }, 'drift.from = the acked base×terminal');
|
|
213
|
+
assert.deepEqual(drift?.to, { kind: 'developer', mode: 'orchestrator', lifecycle: 'terminal' }, 'drift.to = the new orchestrator persona');
|
|
228
214
|
assert.match(drift?.guidance ?? '', /ORCHESTRATOR/i, 'the steer carries the base→orchestrator guidance');
|
|
229
215
|
const stop = evaluateStop(B, { pushedFinal: false, requestedHumanReply: false });
|
|
230
216
|
assert.equal(stop.action, 'reprompt', 'orchestrator B with no live sub, no final → reprompt');
|
package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js}
RENAMED
|
@@ -16,9 +16,7 @@
|
|
|
16
16
|
// promote, no roadmap seeded, no persona_ack drift — it just sets
|
|
17
17
|
// intent=refresh. `node yield --promote` is the explicit opt-in that
|
|
18
18
|
// rides the promote() path (mode→orchestrator + roadmap seeded), and
|
|
19
|
-
//
|
|
20
|
-
// preceding turn_end, the persona-drift steer that promotion would have
|
|
21
|
-
// queued is never delivered (ack left PENDING).
|
|
19
|
+
// leaves persona_ack pending until the fresh-context baseline persists.
|
|
22
20
|
// (2) WHY MODEL-LEVEL, NOT PANE/WINDOW — demote writes lifecycle on the row
|
|
23
21
|
// (setLifecycle→updateNode); recycle's FINISH+MINT half is pushFinal +
|
|
24
22
|
// spawnNode (pure model) — only its pane-respawn is tmux (a deliberately-
|
|
@@ -33,22 +31,17 @@
|
|
|
33
31
|
// or seed a roadmap, or if it committed the ack itself, the A4 'yield
|
|
34
32
|
// --promote' asserts go RED.
|
|
35
33
|
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// is the only tmux artifact, intentionally skipped → recycled:false). For A4 the
|
|
41
|
-
// PURE pre-stop facts are asserted (mode preserved on a plain yield; mode flip +
|
|
42
|
-
// roadmap seeded + ack PENDING on `--promote`); the ack-commit-at-refresh-drain
|
|
43
|
-
// is locked by persona.test.ts ('… then clears on commit'), and the steer-bypass
|
|
44
|
-
// follows because no turn_end ever fires on the yield path.
|
|
34
|
+
// HOST-HOOK CAVEAT — fabrication cannot run the replacement context intro.
|
|
35
|
+
// recycleNode still exercises its finish-and-mint model path; these A4 cases pin
|
|
36
|
+
// the pre-refresh state. canvas-context-intro.test.ts proves the fresh baseline
|
|
37
|
+
// persists before persona_ack advances.
|
|
45
38
|
import { test } from 'node:test';
|
|
46
39
|
import assert from 'node:assert/strict';
|
|
47
|
-
import { createHeadlessHarness } from '
|
|
48
|
-
import { subscribe } from '
|
|
49
|
-
import { recycleNode } from '
|
|
50
|
-
import { commitPersonaAck, personaDrift } from '
|
|
51
|
-
import { hasRoadmap } from '
|
|
40
|
+
import { createHeadlessHarness } from '../helpers/harness.js';
|
|
41
|
+
import { subscribe } from '../../canvas/canvas.js';
|
|
42
|
+
import { recycleNode } from '../../runtime/recycle.js';
|
|
43
|
+
import { commitPersonaAck, personaDrift } from '../../runtime/persona.js';
|
|
44
|
+
import { hasRoadmap } from '../../runtime/roadmap.js';
|
|
52
45
|
/** Normalize the two persona axes off a NodeMeta for deepEqual. */
|
|
53
46
|
function persona(m) {
|
|
54
47
|
return { mode: m.mode, lifecycle: m.lifecycle };
|
|
@@ -117,7 +110,7 @@ test('node lifecycle demote flips lifecycle→terminal IN PLACE; node lifecycle
|
|
|
117
110
|
{
|
|
118
111
|
const fresh = h.node(res.newRoot);
|
|
119
112
|
assert.deepEqual(persona(fresh), { mode: 'base', lifecycle: 'resident' }, 'recycled root is born base×resident (general)');
|
|
120
|
-
assert.deepEqual(fresh.persona_ack, { mode: 'base', lifecycle: 'resident' }, 'fresh root born acked
|
|
113
|
+
assert.deepEqual(fresh.persona_ack, { kind: 'general', mode: 'base', lifecycle: 'resident' }, 'fresh root born acked general×base×resident');
|
|
121
114
|
assert.equal(fresh.parent ?? null, null, 'the fresh root is a root (no parent)');
|
|
122
115
|
}
|
|
123
116
|
}
|
|
@@ -129,9 +122,8 @@ test('node lifecycle demote flips lifecycle→terminal IN PLACE; node lifecycle
|
|
|
129
122
|
// (b) A4 BOUNDARY — plain yield is mode-preserving; `yield --promote` is the
|
|
130
123
|
// explicit one-step promotion + refresh. A plain yield sets intent=refresh
|
|
131
124
|
// only, leaves the base persona acknowledged, and does NOT seed a roadmap.
|
|
132
|
-
// With `--promote`, the mode flips and a roadmap is seeded;
|
|
133
|
-
//
|
|
134
|
-
// refresh-revive with no preceding turn_end to deliver the steer.
|
|
125
|
+
// With `--promote`, the mode flips and a roadmap is seeded; persona drift
|
|
126
|
+
// remains pending until the replacement context persists its baseline.
|
|
135
127
|
// ===========================================================================
|
|
136
128
|
test('A4: a plain yield preserves base mode and does not seed a roadmap',
|
|
137
129
|
// Full-CLI subprocess spawn under load — see the sibling test's budget note.
|
|
@@ -141,7 +133,7 @@ test('A4: a plain yield preserves base mode and does not seed a roadmap',
|
|
|
141
133
|
const A = h.spawnRoot('resident root');
|
|
142
134
|
const B = h.fabricateBrokerNode({ parent: A, kind: 'developer', mode: 'base', lifecycle: 'terminal', status: 'active' });
|
|
143
135
|
subscribe(A, B, true);
|
|
144
|
-
commitPersonaAck(B, { mode: 'base', lifecycle: 'terminal' });
|
|
136
|
+
commitPersonaAck(B, { kind: 'developer', mode: 'base', lifecycle: 'terminal' });
|
|
145
137
|
assert.equal(hasRoadmap(B), false, 'B has no roadmap before a plain yield');
|
|
146
138
|
const y = h.cli(B, ['node', 'yield', 'refresh against the original goal']);
|
|
147
139
|
assert.equal(y.code, 0, `node yield exit 0\n${y.stderr}`);
|
|
@@ -149,10 +141,10 @@ test('A4: a plain yield preserves base mode and does not seed a roadmap',
|
|
|
149
141
|
const b = h.node(B);
|
|
150
142
|
assert.equal(b.mode, 'base', 'plain yield preserves base mode');
|
|
151
143
|
assert.equal(b.intent, 'refresh', 'intent=refresh set by the yield');
|
|
152
|
-
assert.deepEqual(b.persona_ack, { mode: 'base', lifecycle: 'terminal' }, 'plain yield leaves the acknowledged base persona unchanged');
|
|
144
|
+
assert.deepEqual(b.persona_ack, { kind: 'developer', mode: 'base', lifecycle: 'terminal' }, 'plain yield leaves the acknowledged base persona unchanged');
|
|
153
145
|
}
|
|
154
146
|
assert.equal(hasRoadmap(B), false, 'plain yield does not seed a roadmap');
|
|
155
|
-
assert.equal(personaDrift(B), null, 'plain yield leaves no persona
|
|
147
|
+
assert.equal(personaDrift(B), null, 'plain yield leaves no persona transition pending');
|
|
156
148
|
}
|
|
157
149
|
finally {
|
|
158
150
|
await h.dispose();
|
|
@@ -166,7 +158,7 @@ test('A4: yield --promote flips base→orchestrator, seeds a roadmap, and leaves
|
|
|
166
158
|
const A = h.spawnRoot('resident root');
|
|
167
159
|
const B = h.fabricateBrokerNode({ parent: A, kind: 'developer', mode: 'base', lifecycle: 'terminal', status: 'active' });
|
|
168
160
|
subscribe(A, B, true);
|
|
169
|
-
commitPersonaAck(B, { mode: 'base', lifecycle: 'terminal' });
|
|
161
|
+
commitPersonaAck(B, { kind: 'developer', mode: 'base', lifecycle: 'terminal' });
|
|
170
162
|
assert.equal(hasRoadmap(B), false, 'B has no roadmap before yield --promote');
|
|
171
163
|
const y = h.cli(B, ['node', 'yield', '--promote', 'refresh as an orchestrator']);
|
|
172
164
|
assert.equal(y.code, 0, `node yield --promote exit 0\n${y.stderr}`);
|
|
@@ -174,19 +166,16 @@ test('A4: yield --promote flips base→orchestrator, seeds a roadmap, and leaves
|
|
|
174
166
|
const b = h.node(B);
|
|
175
167
|
assert.equal(b.mode, 'orchestrator', 'yield --promote flips base→orchestrator');
|
|
176
168
|
assert.equal(b.intent, 'refresh', 'intent=refresh set by the yield');
|
|
177
|
-
assert.deepEqual(b.persona_ack, { mode: 'base', lifecycle: 'terminal' }, 'ack
|
|
169
|
+
assert.deepEqual(b.persona_ack, { kind: 'developer', mode: 'base', lifecycle: 'terminal' }, 'ack stays base until provider-gated delivery or the fresh-context baseline completes');
|
|
178
170
|
}
|
|
179
171
|
assert.equal(hasRoadmap(B), true, 'yield --promote seeds a roadmap');
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
// turn_end injector would have delivered as a steer. The ack's silent
|
|
183
|
-
// advance at the refresh drain — NOT via a steer — is locked by
|
|
184
|
-
// persona.test.ts; the steer is lost because no turn_end fires on this path.
|
|
172
|
+
// The refresh kickoff carries this framing into the replacement context;
|
|
173
|
+
// persona_ack stays pending until that context's snapshots persist.
|
|
185
174
|
{
|
|
186
175
|
const drift = personaDrift(B);
|
|
187
176
|
assert.ok(drift !== null, 'the orchestration guidance is PENDING after yield --promote (drift detected)');
|
|
188
|
-
assert.deepEqual(drift?.to, { mode: 'orchestrator', lifecycle: 'terminal' }, 'pending drift → orchestrator×terminal');
|
|
189
|
-
assert.match(drift?.guidance ?? '', /ORCHESTRATOR/i, 'the pending
|
|
177
|
+
assert.deepEqual(drift?.to, { kind: 'developer', mode: 'orchestrator', lifecycle: 'terminal' }, 'pending drift → orchestrator×terminal');
|
|
178
|
+
assert.match(drift?.guidance ?? '', /ORCHESTRATOR/i, 'the pending fresh-revive framing carries the orchestration guidance');
|
|
190
179
|
}
|
|
191
180
|
}
|
|
192
181
|
finally {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Run with: npm run test:
|
|
1
|
+
// Run with: npm run test:integration
|
|
2
2
|
//
|
|
3
3
|
// HEADLESS RETARGET (foundation-spec §C.12 + §E). AXIS: LIVE MUTATION of the
|
|
4
4
|
// 2×2 state vector (mode {base,orchestrator} × lifecycle {terminal,resident})
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
// (1) BUG LOCKED — (a) a live lifecycle flip changes the idle-release decision:
|
|
13
13
|
// a resident node is NEVER forced dormant (suppressed), a terminal one with
|
|
14
14
|
// an active live sub IS legitimately awaiting (restored). (b) a live promote
|
|
15
|
-
// flips mode but leaves persona_ack
|
|
16
|
-
// commits the ack itself.
|
|
15
|
+
// flips mode but leaves persona_ack pending for provider-gated delivery.
|
|
17
16
|
// (2) WHY MODEL-LEVEL, NOT PANE/WINDOW — the flip writes lifecycle/mode on the
|
|
18
17
|
// row (updateNode) and the idle-release call is decided by the PURE
|
|
19
18
|
// evaluateStop (stop-guard.ts), which keys on lifecycle + active live subs,
|
|
@@ -28,16 +27,10 @@
|
|
|
28
27
|
// orchestrator PENDING; if promote wrongly committed the ack, drift reads
|
|
29
28
|
// null → the pending-drift assert goes RED.
|
|
30
29
|
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
// leaves the hook-commit proof to the already-pure siblings:
|
|
36
|
-
// • idle-release / resident-suppression commit at agent_end → canvas-stophook-
|
|
37
|
-
// agentend.test.ts ('natural stop while awaiting a live worker → idle-release'
|
|
38
|
-
// and '§5.1.7 resident attended … nothing happens').
|
|
39
|
-
// • persona_ack recompose at turn_end → persona.test.ts ('personaDrift detects
|
|
40
|
-
// base→orchestrator after promote, then clears on commit').
|
|
30
|
+
// HOST-HOOK CAVEAT — fabrication cannot run agent_end or the broker's
|
|
31
|
+
// pre-provider persona gate. This test asserts evaluateStop and personaDrift;
|
|
32
|
+
// canvas-stophook-agentend.test.ts pins idle-release behavior, and
|
|
33
|
+
// canvas-context-intro.test.ts pins transition delivery and guarded ack.
|
|
41
34
|
import { test } from 'node:test';
|
|
42
35
|
import assert from 'node:assert/strict';
|
|
43
36
|
import { createHeadlessHarness } from '../helpers/harness.js';
|
|
@@ -132,15 +125,11 @@ test('live lifecycle flip: terminal→resident suppresses idle-release; resident
|
|
|
132
125
|
}
|
|
133
126
|
});
|
|
134
127
|
// ===========================================================================
|
|
135
|
-
// (b) MODE FLIP — promote: base → orchestrator on a LIVE node.
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// are that promote leaves the ack PENDING (not committed) and personaDrift
|
|
139
|
-
// reports the exact base→orchestrator transition the injector would deliver —
|
|
140
|
-
// the model precondition of the recompose (whose commit is locked by
|
|
141
|
-
// persona.test.ts).
|
|
128
|
+
// (b) MODE FLIP — promote: base → orchestrator on a LIVE node. Promotion leaves
|
|
129
|
+
// persona_ack pending; the next provider request appends and persists the
|
|
130
|
+
// transition before a guarded ack. This test pins that model precondition.
|
|
142
131
|
// ===========================================================================
|
|
143
|
-
test('live mode flip: promote base→orchestrator leaves persona_ack
|
|
132
|
+
test('live mode flip: promote base→orchestrator leaves persona_ack pending for provider-gated delivery',
|
|
144
133
|
// Full-CLI subprocess spawn under load — see the sibling test's budget note.
|
|
145
134
|
{ timeout: 60_000 }, async () => {
|
|
146
135
|
const h = await createHeadlessHarness({ sessionPrefix: 'crtr-live-mode' });
|
|
@@ -150,16 +139,14 @@ test('live mode flip: promote base→orchestrator leaves persona_ack PENDING for
|
|
|
150
139
|
subscribe(A, B, true);
|
|
151
140
|
// Born acked to its own persona (mirroring a real birth) → no spurious
|
|
152
141
|
// drift before the promote (invariant 11).
|
|
153
|
-
commitPersonaAck(B, { mode: 'base', lifecycle: 'terminal' });
|
|
142
|
+
commitPersonaAck(B, { kind: 'developer', mode: 'base', lifecycle: 'terminal' });
|
|
154
143
|
{
|
|
155
144
|
const b = h.node(B);
|
|
156
145
|
assert.deepEqual(persona(b), { mode: 'base', lifecycle: 'terminal' }, 'B born base×terminal');
|
|
157
|
-
assert.deepEqual(b.persona_ack, { mode: 'base', lifecycle: 'terminal' }, 'persona_ack born equal to the initial persona (invariant 11)');
|
|
146
|
+
assert.deepEqual(b.persona_ack, { kind: 'developer', mode: 'base', lifecycle: 'terminal' }, 'persona_ack born equal to the initial persona (invariant 11)');
|
|
158
147
|
assert.equal(personaDrift(B), null, 'no drift before the promote');
|
|
159
148
|
}
|
|
160
|
-
// PROMOTE
|
|
161
|
-
// untouched (no transition). Crucially promote does NOT commit the ack —
|
|
162
|
-
// the drift is left PENDING for the injector.
|
|
149
|
+
// PROMOTE changes mode only and leaves the transition pending.
|
|
163
150
|
{
|
|
164
151
|
const res = h.cli(B, ['node', 'promote', '--kind', 'developer']);
|
|
165
152
|
assert.equal(res.code, 0, `promote exit 0\n${res.stderr}`);
|
|
@@ -168,27 +155,20 @@ test('live mode flip: promote base→orchestrator leaves persona_ack PENDING for
|
|
|
168
155
|
assert.equal(b.lifecycle, 'terminal', 'lifecycle UNCHANGED (promote is mode-only)');
|
|
169
156
|
assert.equal(b.status, 'active', 'status untouched by promote');
|
|
170
157
|
assert.equal(b.intent ?? null, null, 'intent untouched by promote');
|
|
171
|
-
assert.deepEqual(b.persona_ack, { mode: 'base', lifecycle: 'terminal' }, 'promote
|
|
158
|
+
assert.deepEqual(b.persona_ack, { kind: 'developer', mode: 'base', lifecycle: 'terminal' }, 'promote leaves the previous persona acknowledged');
|
|
172
159
|
}
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
// (and then commit). NON-VACUOUS: if promote wrongly committed the ack,
|
|
176
|
-
// personaDrift reads null and the asserts below go RED. The actual delivery
|
|
177
|
-
// + commit (and its idempotence on a second turn) is locked by
|
|
178
|
-
// persona.test.ts ('… then clears on commit').
|
|
160
|
+
// If promotion committed the ack itself, personaDrift would be null and
|
|
161
|
+
// these assertions would fail. Provider delivery is pinned separately.
|
|
179
162
|
{
|
|
180
163
|
const drift = personaDrift(B);
|
|
181
164
|
assert.ok(drift !== null, 'a recompose is pending after the live promote');
|
|
182
|
-
assert.deepEqual(drift?.from, { mode: 'base', lifecycle: 'terminal' }, 'drift.from = the acked base×terminal');
|
|
183
|
-
assert.deepEqual(drift?.to, { mode: 'orchestrator', lifecycle: 'terminal' }, 'drift.to = the new orchestrator persona');
|
|
165
|
+
assert.deepEqual(drift?.from, { kind: 'developer', mode: 'base', lifecycle: 'terminal' }, 'drift.from = the acked base×terminal');
|
|
166
|
+
assert.deepEqual(drift?.to, { kind: 'developer', mode: 'orchestrator', lifecycle: 'terminal' }, 'drift.to = the new orchestrator persona');
|
|
184
167
|
assert.match(drift?.guidance ?? '', /ORCHESTRATOR/i, 'the steer carries the base→orchestrator orchestration guidance');
|
|
185
168
|
}
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// is locked by persona.test.ts.
|
|
190
|
-
commitPersonaAck(B, { mode: 'orchestrator', lifecycle: 'terminal' });
|
|
191
|
-
assert.deepEqual(h.node(B).persona_ack, { mode: 'orchestrator', lifecycle: 'terminal' }, 'persona_ack recomposed to the new persona');
|
|
169
|
+
// Model a successful delivery ack and prove the detector clears.
|
|
170
|
+
commitPersonaAck(B, { kind: 'developer', mode: 'orchestrator', lifecycle: 'terminal' });
|
|
171
|
+
assert.deepEqual(h.node(B).persona_ack, { kind: 'developer', mode: 'orchestrator', lifecycle: 'terminal' }, 'persona_ack recomposed to the new persona');
|
|
192
172
|
assert.equal(personaDrift(B), null, 'no fresh drift after the ack commit (idempotent recompose)');
|
|
193
173
|
}
|
|
194
174
|
finally {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Run with: npm run test:
|
|
1
|
+
// Run with: npm run test:integration
|
|
2
2
|
//
|
|
3
3
|
// HEADLESS RETARGET (foundation-spec §C.13 + §E). Covers the session-picker fix
|
|
4
4
|
// (resume by ABSOLUTE session-file path, cwd-immune) and the double-revive guard
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
// goes RED.
|
|
29
29
|
import { test, before, after, beforeEach } from 'node:test';
|
|
30
30
|
import assert from 'node:assert/strict';
|
|
31
|
-
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
31
|
+
import { mkdtempSync, realpathSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
32
32
|
import { tmpdir } from 'node:os';
|
|
33
33
|
import { join } from 'node:path';
|
|
34
34
|
import { spawn, spawnSync } from 'node:child_process';
|
|
35
|
-
import { createNode, getNode, subscribe, updateNode } from '../../canvas/canvas.js';
|
|
35
|
+
import { createNode, deleteNode, getNode, subscribe, updateNode } from '../../canvas/canvas.js';
|
|
36
36
|
import { armCron, hasPendingCancelOnWakeCron } from '../../canvas/crons.js';
|
|
37
37
|
import { readInboxSince } from '../../feed/inbox.js';
|
|
38
38
|
import { closeDb } from '../../canvas/db.js';
|
|
@@ -46,6 +46,7 @@ import { InputError } from '../../io.js';
|
|
|
46
46
|
import { createHarness } from '../helpers/harness.js';
|
|
47
47
|
import { bindTestFleet } from '../helpers/fleet.js';
|
|
48
48
|
import { resetFleetForTests } from '../../runtime/fleet.js';
|
|
49
|
+
import { emptyContextExposureState, exposureTarget, initializeForkContextExposure, loadContextExposureState, registerExposure, saveContextExposureState, } from '../../substrate/injected-store.js';
|
|
49
50
|
let home;
|
|
50
51
|
function node(id, over = {}) {
|
|
51
52
|
return {
|
|
@@ -80,7 +81,10 @@ function disposableLivePid() {
|
|
|
80
81
|
return c.pid;
|
|
81
82
|
}
|
|
82
83
|
before(() => {
|
|
83
|
-
|
|
84
|
+
// realpath, not the raw mkdtemp path: on macOS `tmpdir()` is the /var symlink
|
|
85
|
+
// to /private/var, and the runtime resolves a fork's durable source file, so a
|
|
86
|
+
// raw temp path would compare unequal to the very path the runtime replays.
|
|
87
|
+
home = realpathSync(mkdtempSync(join(tmpdir(), 'crtr-revive-')));
|
|
84
88
|
process.env['CRTR_HOME'] = home;
|
|
85
89
|
});
|
|
86
90
|
beforeEach(() => {
|
|
@@ -345,7 +349,7 @@ test('reviveNode fresh-fallback clears the stale session identity (Major-3: no p
|
|
|
345
349
|
await h.dispose();
|
|
346
350
|
}
|
|
347
351
|
});
|
|
348
|
-
test('
|
|
352
|
+
test('unbound forks replay one durable source file for node-id, path, and UUID operands', () => {
|
|
349
353
|
const originalLaunch = headlessBrokerHost.launch;
|
|
350
354
|
const tf = bindTestFleet();
|
|
351
355
|
const forkFile = join(home, 'ordinary-fork-source.jsonl');
|
|
@@ -353,26 +357,41 @@ test('an unbound ordinary fork replays its immutable source with and without its
|
|
|
353
357
|
try {
|
|
354
358
|
mkdirSync(home, { recursive: true });
|
|
355
359
|
writeFileSync(forkFile, '{}\n', 'utf8');
|
|
360
|
+
createNode(node('ordinary-fork-source', { cwd: home, pi_session_file: forkFile }));
|
|
361
|
+
const sourceExposure = emptyContextExposureState();
|
|
362
|
+
registerExposure(exposureTarget(sourceExposure, 'system'), 'source-system-only', 'content');
|
|
363
|
+
registerExposure(exposureTarget(sourceExposure, 'transcript'), 'source-transcript', 'content');
|
|
364
|
+
saveContextExposureState('ordinary-fork-source', sourceExposure);
|
|
356
365
|
headlessBrokerHost.launch = (nodeId, inv) => {
|
|
357
366
|
launches.push([...inv.argv]);
|
|
358
367
|
const handle = { pid: disposableLivePid(), exited: new Promise(() => { }) };
|
|
359
368
|
tf.fleet.register(nodeId, handle);
|
|
360
369
|
return handle;
|
|
361
370
|
};
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
371
|
+
const operands = ['ordinary-fork-source', forkFile, '019e8ce3-322e-7000-8000-000000000001'];
|
|
372
|
+
for (const [index, operand] of operands.entries()) {
|
|
373
|
+
const destination = `ordinary-fork-${index}`;
|
|
374
|
+
createNode(node(destination, {
|
|
375
|
+
cwd: home,
|
|
376
|
+
fork_from: operand,
|
|
377
|
+
fork_source_file: forkFile,
|
|
378
|
+
}));
|
|
379
|
+
initializeForkContextExposure(destination, 'ordinary-fork-source');
|
|
380
|
+
if (index === 1)
|
|
381
|
+
writeGoal(destination, 'continue the forked task');
|
|
382
|
+
}
|
|
383
|
+
deleteNode('ordinary-fork-source');
|
|
384
|
+
for (let index = 0; index < operands.length; index++) {
|
|
385
|
+
const destination = `ordinary-fork-${index}`;
|
|
386
|
+
reviveNode(destination, { resume: index !== 1 });
|
|
387
|
+
const argv = launches[index];
|
|
388
|
+
const fork = argv.indexOf('--fork');
|
|
389
|
+
assert.equal(argv[fork + 1], forkFile, `${operands[index]} retries from the durable resolved file`);
|
|
390
|
+
const exposure = loadContextExposureState(destination);
|
|
391
|
+
assert.deepEqual(exposure.exposures.get('source-transcript'), { transcript: 3 });
|
|
392
|
+
assert.equal(exposure.exposures.has('source-system-only'), false, 'forks never inherit source system exposure');
|
|
393
|
+
}
|
|
394
|
+
assert.equal(launches[1].at(-1), readGoal('ordinary-fork-1'), 'a goal-carrying fork preserves its exact goal');
|
|
376
395
|
}
|
|
377
396
|
finally {
|
|
378
397
|
headlessBrokerHost.launch = originalLaunch;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|