@cohortapp/agent-sdk 2.16.0 → 2.18.4
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/.claude/settings.json +18 -0
- package/.env.example +23 -7
- package/README.md +1 -0
- package/bin/maestro.mjs +62 -0
- package/docs/guides/billing-console-keys.md +60 -0
- package/docs/guides/front-door-session.md +54 -9
- package/docs/guides/mac-mini.md +20 -25
- package/docs/guides/poller-daemon-setup.md +4 -1
- package/docs/guides/setup-wizard.md +1 -1
- package/docs/runbooks/fleet-rollout.md +156 -0
- package/docs/runbooks/mac-mini-bootstrap.md +12 -14
- package/lib/action-executor.js +19 -3
- package/lib/budget-guard.mjs +279 -3
- package/lib/channels/base-adapter.mjs +3 -1
- package/lib/channels/contract.mjs +2 -1
- package/lib/channels/inbox-item.mjs +8 -0
- package/lib/claude-bin.mjs +5 -6
- package/lib/cli/doctor-checks.mjs +141 -10
- package/lib/cli/global-setup-extras.mjs +5 -1
- package/lib/cli/inbox.mjs +100 -15
- package/lib/cli/seat-auth.mjs +463 -0
- package/lib/cli/session.mjs +80 -12
- package/lib/collective/capture.mjs +8 -6
- package/lib/collective/global-config.mjs +63 -1
- package/lib/collective/presence.mjs +142 -5
- package/lib/comms/send-gate.mjs +559 -1
- package/lib/context/budget.mjs +327 -0
- package/lib/context/history-scope.mjs +138 -0
- package/lib/diagnostics/alerts.mjs +49 -0
- package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
- package/lib/engine/agents/definitions.mjs +343 -0
- package/lib/engine/agents/persist.mjs +275 -0
- package/lib/engine/agents/runtime.mjs +748 -0
- package/lib/engine/agents/usage.mjs +95 -0
- package/lib/engine/auth-status.mjs +139 -0
- package/lib/engine/budget.mjs +194 -0
- package/lib/engine/cli.mjs +1204 -0
- package/lib/engine/commands/index.mjs +269 -0
- package/lib/engine/context/budget.mjs +219 -0
- package/lib/engine/context/cache.mjs +125 -0
- package/lib/engine/context/child-env.mjs +215 -0
- package/lib/engine/context/compaction.mjs +342 -0
- package/lib/engine/context/images.mjs +90 -0
- package/lib/engine/context/instructions.mjs +327 -0
- package/lib/engine/context/lazy-instructions.mjs +169 -0
- package/lib/engine/context/manager.mjs +182 -0
- package/lib/engine/context/real-path.mjs +91 -0
- package/lib/engine/context/secret-values.mjs +163 -0
- package/lib/engine/context/settings.mjs +274 -0
- package/lib/engine/context/stream-input.mjs +159 -0
- package/lib/engine/guard.mjs +152 -0
- package/lib/engine/hooks.mjs +713 -0
- package/lib/engine/loop.mjs +560 -0
- package/lib/engine/mcp/client.mjs +254 -0
- package/lib/engine/mcp/config.mjs +301 -0
- package/lib/engine/mcp/http.mjs +201 -0
- package/lib/engine/mcp/index.mjs +146 -0
- package/lib/engine/mcp/jsonrpc.mjs +147 -0
- package/lib/engine/mcp/naming.mjs +66 -0
- package/lib/engine/mcp/resources.mjs +89 -0
- package/lib/engine/mcp/results.mjs +133 -0
- package/lib/engine/mcp/stdio.mjs +137 -0
- package/lib/engine/mcp/supervisor.mjs +116 -0
- package/lib/engine/messages.mjs +104 -0
- package/lib/engine/output/json.mjs +164 -0
- package/lib/engine/output/stream-json.mjs +266 -0
- package/lib/engine/permissions.mjs +845 -0
- package/lib/engine/process-identity.mjs +164 -0
- package/lib/engine/process-tree.mjs +551 -0
- package/lib/engine/prompt.mjs +60 -0
- package/lib/engine/session/store.mjs +299 -0
- package/lib/engine/session-runtime/args.mjs +97 -0
- package/lib/engine/session-runtime/host.mjs +143 -0
- package/lib/engine/session-runtime/inbox.mjs +122 -0
- package/lib/engine/session-runtime/notifications.mjs +129 -0
- package/lib/engine/session-runtime/registry.mjs +328 -0
- package/lib/engine/session-runtime/runner.mjs +344 -0
- package/lib/engine/session-runtime/socket.mjs +212 -0
- package/lib/engine/session-runtime/wakeup.mjs +115 -0
- package/lib/engine/skills/index.mjs +321 -0
- package/lib/engine/tools/bash-background.mjs +533 -0
- package/lib/engine/tools/bash.mjs +216 -0
- package/lib/engine/tools/edit.mjs +97 -0
- package/lib/engine/tools/glob.mjs +81 -0
- package/lib/engine/tools/grep.mjs +224 -0
- package/lib/engine/tools/index.mjs +84 -0
- package/lib/engine/tools/list-agents.mjs +32 -0
- package/lib/engine/tools/ls.mjs +127 -0
- package/lib/engine/tools/monitor.mjs +82 -0
- package/lib/engine/tools/notebook-edit.mjs +218 -0
- package/lib/engine/tools/read.mjs +103 -0
- package/lib/engine/tools/schedule-wakeup.mjs +45 -0
- package/lib/engine/tools/schema.mjs +144 -0
- package/lib/engine/tools/send-message.mjs +77 -0
- package/lib/engine/tools/session.mjs +70 -0
- package/lib/engine/tools/todo.mjs +144 -0
- package/lib/engine/tools/toolsearch.mjs +217 -0
- package/lib/engine/tools/walk.mjs +193 -0
- package/lib/engine/tools/web-switch.mjs +31 -0
- package/lib/engine/tools/webfetch-html.mjs +387 -0
- package/lib/engine/tools/webfetch-net.mjs +340 -0
- package/lib/engine/tools/webfetch.mjs +198 -0
- package/lib/engine/tools/websearch.mjs +91 -0
- package/lib/engine/tools/workflow.mjs +95 -0
- package/lib/engine/tools/write.mjs +76 -0
- package/lib/engine/tui/line-editor.mjs +137 -0
- package/lib/engine/tui/render.mjs +86 -0
- package/lib/engine/tui/tui.mjs +274 -0
- package/lib/engine/wire/anthropic-messages.mjs +263 -0
- package/lib/engine/wire/effort.mjs +36 -0
- package/lib/engine/wire/errors.mjs +496 -0
- package/lib/engine/wire/http.mjs +441 -0
- package/lib/engine/wire/index.mjs +76 -0
- package/lib/engine/wire/openai-chat.mjs +332 -0
- package/lib/engine/wire/prompt-cache.mjs +79 -0
- package/lib/engine/wire/search.mjs +140 -0
- package/lib/engine/wire/sse.mjs +114 -0
- package/lib/engine/wire/stall.mjs +349 -0
- package/lib/engine/wire/token-provider.mjs +175 -0
- package/lib/engine/wire/usage.mjs +192 -0
- package/lib/engine/workflow/host.mjs +524 -0
- package/lib/engine/workflow/journal.mjs +188 -0
- package/lib/engine/workflow/json-schema.mjs +171 -0
- package/lib/engine/workflow/meta.mjs +329 -0
- package/lib/engine/workflow/notifications.mjs +52 -0
- package/lib/engine/workflow/runtime.mjs +447 -0
- package/lib/engine/workflow/sandbox.mjs +534 -0
- package/lib/engine/workflow/worker.mjs +141 -0
- package/lib/engine/workflow/worktree.mjs +74 -0
- package/lib/execution/disposition.mjs +1 -1
- package/lib/execution/intake.mjs +10 -0
- package/lib/execution/surface-policy.mjs +15 -0
- package/lib/learning/curator.mjs +8 -6
- package/lib/learning/reflect.mjs +8 -6
- package/lib/model-router/catalog/cohort.yaml +137 -0
- package/lib/model-router/catalog.mjs +118 -1
- package/lib/model-router/economics.mjs +9 -0
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +95 -3
- package/lib/model-router/spawn.mjs +46 -47
- package/lib/model-router/taxonomy.mjs +126 -4
- package/lib/org/cost-sync.mjs +141 -11
- package/lib/org/inbound/broadcast.mjs +289 -0
- package/lib/org/inbound/collective.mjs +375 -0
- package/lib/org/inbound/directedness.mjs +96 -8
- package/lib/org/inbound/facts.mjs +82 -4
- package/lib/org/inbound/hydrate.mjs +555 -51
- package/lib/org/inbound/project.mjs +22 -0
- package/lib/org/inbound/surfaces.mjs +14 -0
- package/lib/org/llm-token.mjs +879 -0
- package/lib/org/mesh.mjs +61 -0
- package/lib/org/messaging.mjs +3 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +15 -0
- package/lib/org/quota.mjs +520 -0
- package/lib/org/tool-surface.mjs +104 -16
- package/lib/org/ui-parity.mjs +16 -1
- package/lib/org/work-ledger.mjs +37 -6
- package/lib/rate-guard.mjs +114 -1
- package/lib/resource-governor.mjs +41 -6
- package/lib/runtime/adapter.mjs +823 -0
- package/lib/runtime/child-env.mjs +191 -0
- package/lib/runtime/legacy-shell-guard.mjs +97 -0
- package/lib/runtime/seat-engine.mjs +162 -0
- package/lib/session/ask-ledger.mjs +271 -0
- package/lib/session/current-work.mjs +676 -0
- package/lib/session/feed-core.mjs +40 -3
- package/lib/session/launch-args.mjs +56 -4
- package/lib/session/status-summary.mjs +26 -9
- package/lib/session/upgrade-notice.mjs +42 -0
- package/lib/setup/claude-probe.mjs +117 -13
- package/lib/setup/enrich.mjs +13 -10
- package/lib/setup/sections/model.mjs +39 -13
- package/lib/telemetry/collect.mjs +208 -9
- package/lib/upgrade/ignored-drift.mjs +105 -0
- package/lib/voice/post-call-brief.mjs +30 -17
- package/package.json +15 -3
- package/plugins/maestro-skills/skills/board-work.md +5 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
- package/plugins/maestro-skills/skills/main-session.md +18 -7
- package/scripts/ci/check-tarball-fidelity.mjs +126 -2
- package/scripts/ci/run-tests.mjs +47 -19
- package/scripts/cohort-llm/api-key-helper.mjs +92 -0
- package/scripts/collective/hook-runner.mjs +29 -2
- package/scripts/continuous-monitor.sh +13 -0
- package/scripts/cost/track-claude-usage.mjs +15 -0
- package/scripts/daemon/agent-daemon.mjs +408 -20
- package/scripts/daemon/assurance.mjs +48 -12
- package/scripts/daemon/cadence-consumer.mjs +218 -68
- package/scripts/daemon/cadence-handlers.mjs +73 -4
- package/scripts/daemon/classifier.mjs +75 -26
- package/scripts/daemon/context-compiler.mjs +104 -59
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +804 -157
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/lib/session-router.mjs +310 -42
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +121 -12
- package/scripts/daemon/responder.mjs +315 -146
- package/scripts/daemon/sdk-version.mjs +98 -16
- package/scripts/eval/probe-gateway.mjs +635 -0
- package/scripts/eval/replay/extract.mjs +270 -0
- package/scripts/eval/replay/grade.mjs +260 -0
- package/scripts/eval/replay/lib/config.mjs +50 -0
- package/scripts/eval/replay/lib/effects.mjs +65 -0
- package/scripts/eval/replay/lib/fixture.mjs +188 -0
- package/scripts/eval/replay/lib/judge.mjs +72 -0
- package/scripts/eval/replay/lib/redact.mjs +136 -0
- package/scripts/eval/replay/lib/sandbox.mjs +170 -0
- package/scripts/eval/replay/lib/schema-check.mjs +63 -0
- package/scripts/eval/replay/lib/transcript.mjs +76 -0
- package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
- package/scripts/eval/replay/report.mjs +185 -0
- package/scripts/eval/replay/run.mjs +404 -0
- package/scripts/fleet/rollout.mjs +1094 -0
- package/scripts/hooks/pre-send-audit.sh +36 -245
- package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
- package/scripts/hooks/validate-state-yaml.sh +190 -0
- package/scripts/huddle/huddle-llm.mjs +361 -0
- package/scripts/huddle/huddle-server.mjs +46 -121
- package/scripts/local-triggers/autoupdate.sh +448 -78
- package/scripts/local-triggers/run-trigger.sh +13 -0
- package/scripts/maintenance/pin-integrity.mjs +364 -0
- package/scripts/poll-slack-events.sh +41 -9
- package/scripts/poller/slack-socket-mode.mjs +28 -3
- package/scripts/session/supervisor.mjs +80 -13
- package/scripts/spawn-session.sh +13 -0
- package/bin/maestro.test.mjs +0 -1574
- package/lib/action-executor.test.mjs +0 -871
- package/lib/archetype.test.mjs +0 -132
- package/lib/assurance/plan-note.test.mjs +0 -234
- package/lib/assurance/room-budget.test.mjs +0 -486
- package/lib/assurance/tier.test.mjs +0 -174
- package/lib/autonomy.test.mjs +0 -66
- package/lib/backlog.test.mjs +0 -302
- package/lib/backup/policy.test.mjs +0 -305
- package/lib/budget-escalate.test.mjs +0 -232
- package/lib/budget-guard.envelope.test.mjs +0 -476
- package/lib/budget-guard.test.mjs +0 -427
- package/lib/cadence-bus-requeue.test.mjs +0 -83
- package/lib/cadence-bus-schedule.test.mjs +0 -194
- package/lib/cadence-bus.test.mjs +0 -720
- package/lib/cadences.test.mjs +0 -230
- package/lib/capability/inventory.test.mjs +0 -232
- package/lib/capability.test.mjs +0 -78
- package/lib/channels/base-adapter.test.mjs +0 -590
- package/lib/channels/channels.test.mjs +0 -371
- package/lib/channels/contract.test.mjs +0 -162
- package/lib/channels/inbox-item.test.mjs +0 -368
- package/lib/channels/orgmail/adapter.test.mjs +0 -448
- package/lib/channels/pairing.test.mjs +0 -270
- package/lib/channels/repeat-suppressor.test.mjs +0 -134
- package/lib/channels/slack-adapter.test.mjs +0 -212
- package/lib/channels/telegram-adapter.test.mjs +0 -306
- package/lib/channels/voice/adapter.test.mjs +0 -278
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
- package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
- package/lib/charter.test.mjs +0 -89
- package/lib/claude-bin.test.mjs +0 -131
- package/lib/cli/board.test.mjs +0 -227
- package/lib/cli/design.test.mjs +0 -270
- package/lib/cli/doctor-checks.test.mjs +0 -336
- package/lib/cli/global-setup-extras.test.mjs +0 -462
- package/lib/cli/inbox.test.mjs +0 -230
- package/lib/cli/session-ack.test.mjs +0 -63
- package/lib/cli/session.test.mjs +0 -613
- package/lib/collective/capture.test.mjs +0 -121
- package/lib/collective/cards.test.mjs +0 -114
- package/lib/collective/config.test.mjs +0 -123
- package/lib/collective/global-config.test.mjs +0 -220
- package/lib/collective/global-skills.test.mjs +0 -126
- package/lib/collective/presence.test.mjs +0 -95
- package/lib/collective/recall.test.mjs +0 -116
- package/lib/collective/vendor-skills.test.mjs +0 -306
- package/lib/comms/send-gate.test.mjs +0 -770
- package/lib/comms.test.mjs +0 -41
- package/lib/cost/ledger-row.test.mjs +0 -183
- package/lib/design/design-md.test.mjs +0 -318
- package/lib/design/fixtures/DESIGN.golden.md +0 -238
- package/lib/design/fixtures/PRODUCT.golden.md +0 -67
- package/lib/design/fixtures/foundation.json +0 -133
- package/lib/design/refresh-gate.test.mjs +0 -144
- package/lib/design/write.test.mjs +0 -241
- package/lib/diagnostics/alerts.test.mjs +0 -318
- package/lib/diagnostics/backup-freshness.test.mjs +0 -185
- package/lib/diagnostics/counters.test.mjs +0 -206
- package/lib/diagnostics/events.test.mjs +0 -290
- package/lib/diagnostics/otel.test.mjs +0 -196
- package/lib/diagnostics/trace.test.mjs +0 -251
- package/lib/env-compat.test.mjs +0 -104
- package/lib/execution/disposition.test.mjs +0 -553
- package/lib/execution/drive.test.mjs +0 -270
- package/lib/execution/effects.test.mjs +0 -344
- package/lib/execution/intake.test.mjs +0 -389
- package/lib/execution/journal.test.mjs +0 -261
- package/lib/execution/match.test.mjs +0 -235
- package/lib/execution/pipeline.test.mjs +0 -392
- package/lib/execution/route.test.mjs +0 -186
- package/lib/execution/surface-policy.test.mjs +0 -162
- package/lib/fs-atomic.test.mjs +0 -72
- package/lib/fs-ownership.test.mjs +0 -158
- package/lib/goals/admission.test.mjs +0 -164
- package/lib/goals/classify.test.mjs +0 -167
- package/lib/goals/collaborate.test.mjs +0 -336
- package/lib/goals/gaps.test.mjs +0 -284
- package/lib/goals/loop.test.mjs +0 -845
- package/lib/hooks/bus.test.mjs +0 -387
- package/lib/identity/persona.test.mjs +0 -142
- package/lib/kpi-sensors.test.mjs +0 -278
- package/lib/kpi.test.mjs +0 -244
- package/lib/learning/config.test.mjs +0 -75
- package/lib/learning/counters.test.mjs +0 -69
- package/lib/learning/curator-consolidate.test.mjs +0 -238
- package/lib/learning/curator.test.mjs +0 -106
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.test.mjs +0 -125
- package/lib/learning/skill-writer.test.mjs +0 -210
- package/lib/mandate/audit.test.mjs +0 -195
- package/lib/mandate/contract.test.mjs +0 -185
- package/lib/mandate/derive.test.mjs +0 -274
- package/lib/mandate/model.test.mjs +0 -164
- package/lib/mandate/refresh.test.mjs +0 -389
- package/lib/mcp/server.test.mjs +0 -426
- package/lib/model-router/auth-profiles.test.mjs +0 -580
- package/lib/model-router/catalog.test.mjs +0 -385
- package/lib/model-router/economics.test.mjs +0 -438
- package/lib/model-router/failover.test.mjs +0 -439
- package/lib/model-router/health.test.mjs +0 -338
- package/lib/model-router/integration-coverage.test.mjs +0 -831
- package/lib/model-router/integration.test.mjs +0 -564
- package/lib/model-router/ledger.test.mjs +0 -415
- package/lib/model-router/llm-task.test.mjs +0 -392
- package/lib/model-router/org-credentials.test.mjs +0 -265
- package/lib/model-router/pricing-refresh.test.mjs +0 -286
- package/lib/model-router/reconcile.test.mjs +0 -316
- package/lib/model-router/repair.test.mjs +0 -180
- package/lib/model-router/spawn.test.mjs +0 -446
- package/lib/model-router/taxonomy.test.mjs +0 -410
- package/lib/model-router.test.mjs +0 -1207
- package/lib/org/activity.test.mjs +0 -134
- package/lib/org/approvals.test.mjs +0 -216
- package/lib/org/awareness.test.mjs +0 -159
- package/lib/org/board-mine-cache.test.mjs +0 -53
- package/lib/org/board.test.mjs +0 -187
- package/lib/org/bootstrap-context.test.mjs +0 -153
- package/lib/org/client.test.mjs +0 -1206
- package/lib/org/cohort-client.test.mjs +0 -126
- package/lib/org/cost-sync.test.mjs +0 -153
- package/lib/org/doctor.test.mjs +0 -346
- package/lib/org/engagement-ledger.test.mjs +0 -112
- package/lib/org/engagement.test.mjs +0 -739
- package/lib/org/handoff.test.mjs +0 -269
- package/lib/org/inbound/directedness.test.mjs +0 -668
- package/lib/org/inbound/facts.test.mjs +0 -471
- package/lib/org/inbound/hydrate.test.mjs +0 -453
- package/lib/org/inbound/index.test.mjs +0 -429
- package/lib/org/inbound/project.test.mjs +0 -287
- package/lib/org/integration-tools.test.mjs +0 -160
- package/lib/org/keys.test.mjs +0 -92
- package/lib/org/knowledge.test.mjs +0 -326
- package/lib/org/leases.test.mjs +0 -235
- package/lib/org/mesh-directives.test.mjs +0 -110
- package/lib/org/mesh-integration.test.mjs +0 -127
- package/lib/org/mesh.test.mjs +0 -400
- package/lib/org/messaging.test.mjs +0 -471
- package/lib/org/param-contract.test.mjs +0 -477
- package/lib/org/policy.test.mjs +0 -237
- package/lib/org/protocol.checksum.test.mjs +0 -90
- package/lib/org/protocol.test.mjs +0 -323
- package/lib/org/push.test.mjs +0 -792
- package/lib/org/registry.test.mjs +0 -100
- package/lib/org/resource-tools.test.mjs +0 -361
- package/lib/org/tool-access.test.mjs +0 -144
- package/lib/org/tool-surface-integration.test.mjs +0 -120
- package/lib/org/tool-surface.test.mjs +0 -1268
- package/lib/org/typing.test.mjs +0 -291
- package/lib/org/ui-parity.test.mjs +0 -560
- package/lib/org/verify.test.mjs +0 -194
- package/lib/org/work-ledger.test.mjs +0 -273
- package/lib/plan/adoption-e2e.test.mjs +0 -366
- package/lib/plan/budget-enforcement.test.mjs +0 -400
- package/lib/plan/compile.test.mjs +0 -382
- package/lib/plan/emit.test.mjs +0 -269
- package/lib/plan/explain.test.mjs +0 -188
- package/lib/prompts/parallelism.test.mjs +0 -177
- package/lib/rag/rag.test.mjs +0 -505
- package/lib/rate-guard.test.mjs +0 -272
- package/lib/reactive-gate.test.mjs +0 -57
- package/lib/render.test.mjs +0 -68
- package/lib/resource-governor.test.mjs +0 -488
- package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
- package/lib/scheduling/jitter.test.mjs +0 -140
- package/lib/secrets/broker.test.mjs +0 -280
- package/lib/secrets/providers.test.mjs +0 -274
- package/lib/security/audit-engine.test.mjs +0 -424
- package/lib/security/coerce-args.test.mjs +0 -281
- package/lib/security/dangerous-tools.test.mjs +0 -68
- package/lib/security/external-content.test.mjs +0 -84
- package/lib/security/redact.test.mjs +0 -441
- package/lib/security/secret-equal.test.mjs +0 -55
- package/lib/session/config.test.mjs +0 -92
- package/lib/session/feed-core.test.mjs +0 -198
- package/lib/session/first-run.test.mjs +0 -121
- package/lib/session/frontdoor.test.mjs +0 -205
- package/lib/session/handoffs.test.mjs +0 -183
- package/lib/session/identity.test.mjs +0 -180
- package/lib/session/inbox-claims.test.mjs +0 -286
- package/lib/session/launch-args.test.mjs +0 -157
- package/lib/session/liveness.test.mjs +0 -100
- package/lib/session/status-summary.test.mjs +0 -118
- package/lib/session-permissions.test.mjs +0 -120
- package/lib/setup/claude-probe.test.mjs +0 -187
- package/lib/setup/completeness.test.mjs +0 -110
- package/lib/setup/context-pack.test.mjs +0 -89
- package/lib/setup/enrich.test.mjs +0 -115
- package/lib/setup/enroll-from-cohort.test.mjs +0 -300
- package/lib/setup/integration.test.mjs +0 -162
- package/lib/setup/io.test.mjs +0 -77
- package/lib/setup/runner.test.mjs +0 -132
- package/lib/setup/sections/identity.test.mjs +0 -234
- package/lib/setup/sections/inventory.test.mjs +0 -198
- package/lib/setup/sections/learning.test.mjs +0 -81
- package/lib/setup/sections/mandate.test.mjs +0 -388
- package/lib/setup/sections/messaging.test.mjs +0 -127
- package/lib/setup/sections/model.test.mjs +0 -240
- package/lib/setup/sections/org.test.mjs +0 -346
- package/lib/setup/sections/orgmail.test.mjs +0 -118
- package/lib/setup/sections/recovery.test.mjs +0 -98
- package/lib/setup/sections/subagents.test.mjs +0 -429
- package/lib/setup/sections/verify.test.mjs +0 -175
- package/lib/setup/sot.test.mjs +0 -81
- package/lib/setup/state.test.mjs +0 -115
- package/lib/singleton.test.mjs +0 -151
- package/lib/subagents/cli.test.mjs +0 -389
- package/lib/subagents/client.test.mjs +0 -309
- package/lib/subagents/gap.test.mjs +0 -234
- package/lib/subagents/lock.test.mjs +0 -248
- package/lib/subagents/manifest.test.mjs +0 -175
- package/lib/subagents/refs.test.mjs +0 -204
- package/lib/subagents/resolve.test.mjs +0 -422
- package/lib/subagents/schema.test.mjs +0 -328
- package/lib/telemetry/alerts.test.mjs +0 -109
- package/lib/telemetry/collect.test.mjs +0 -1274
- package/lib/tool-definitions-integration.test.mjs +0 -83
- package/lib/tool-definitions.test.mjs +0 -437
- package/lib/upgrade/global-refresh.test.mjs +0 -65
- package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
- package/lib/upgrade/post-steps.test.mjs +0 -200
- package/lib/upgrade/verify.test.mjs +0 -164
- package/lib/util/fetch-timeout.test.mjs +0 -202
- package/lib/util/reconnect.test.mjs +0 -369
- package/lib/util/unhandled.test.mjs +0 -216
- package/lib/voice/outbound.test.mjs +0 -69
- package/lib/voice/session-rotation.test.mjs +0 -114
- package/lib/voice/stt.test.mjs +0 -226
- package/lib/voice/voice.test.mjs +0 -990
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
- package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
- package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
- package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
- package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
- package/scripts/ci/check-skill-packs.test.mjs +0 -495
- package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
- package/scripts/ci/check.test.mjs +0 -194
- package/scripts/ci/conformance-org-api.test.mjs +0 -425
- package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
- package/scripts/collective/hook-runner.test.mjs +0 -173
- package/scripts/cost/fleet-digest.test.mjs +0 -207
- package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
- package/scripts/cost/track-claude-usage.test.mjs +0 -148
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
- package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
- package/scripts/daemon/agent-daemon.test.mjs +0 -995
- package/scripts/daemon/assurance-e2e.test.mjs +0 -613
- package/scripts/daemon/assurance.test.mjs +0 -1791
- package/scripts/daemon/board-mirror.test.mjs +0 -165
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
- package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
- package/scripts/daemon/cadence-consumer.test.mjs +0 -776
- package/scripts/daemon/cadence-handlers.test.mjs +0 -837
- package/scripts/daemon/classifier-identity.test.mjs +0 -137
- package/scripts/daemon/classifier.test.mjs +0 -266
- package/scripts/daemon/classify-kind.test.mjs +0 -40
- package/scripts/daemon/context-compiler.test.mjs +0 -300
- package/scripts/daemon/deliver.test.mjs +0 -564
- package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
- package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
- package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
- package/scripts/daemon/execution-ladder.test.mjs +0 -470
- package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
- package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
- package/scripts/daemon/inbox-deferral.test.mjs +0 -336
- package/scripts/daemon/inbox-wake.test.mjs +0 -199
- package/scripts/daemon/integration.test.mjs +0 -149
- package/scripts/daemon/lib/self-echo.test.mjs +0 -153
- package/scripts/daemon/lib/session-router.test.mjs +0 -295
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -344
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -185
- package/scripts/daemon/sdk-version.test.mjs +0 -31
- package/scripts/daemon/session-lock.test.mjs +0 -252
- package/scripts/daemon/session-outcomes.test.mjs +0 -533
- package/scripts/daemon/typing-registry.test.mjs +0 -102
- package/scripts/hooks/pre-send-audit.test.mjs +0 -354
- package/scripts/huddle/huddle-prompt.test.mjs +0 -176
- package/scripts/local-triggers/autoupdate.test.mjs +0 -518
- package/scripts/local-triggers/generate-plists.test.mjs +0 -456
- package/scripts/media-generation/brand-clause.test.mjs +0 -135
- package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
- package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
- package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
- package/scripts/poller/slack-socket-mode.test.mjs +0 -805
- package/scripts/poller-launchd/install.test.mjs +0 -243
- package/scripts/restore-from-backup.test.mjs +0 -181
- package/scripts/session/feed.test.mjs +0 -196
- package/scripts/session/supervisor-sh.test.mjs +0 -218
- package/scripts/session/supervisor.test.mjs +0 -482
- package/scripts/setup/configure-macos.test.mjs +0 -306
- package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
- package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
- package/scripts/setup/generate-capability.test.mjs +0 -134
- package/scripts/setup/init-agent.test.mjs +0 -370
- package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
- package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
- package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cohort-client.test.mjs — RETIRED shim re-exporting the aligned client.mjs.
|
|
3
|
-
*
|
|
4
|
-
* The old module spoke a defunct contract (PUT `${apiUrl}/{orgId}/agents/{id}`,
|
|
5
|
-
* POST `${base}/agents/{id}/status`, GET `${base}/agents`). It is now a thin
|
|
6
|
-
* re-export of lib/org/client.mjs, so these tests assert the NEW wire shape:
|
|
7
|
-
* - publishEntry → POST /v1/registry.register (Bearer + x-idempotency-key)
|
|
8
|
-
* - fetchDirectory → GET /v1/directory (parses members/agents/array shapes)
|
|
9
|
-
* - heartbeat → POST /v1/presence.beat (liveness snapshot)
|
|
10
|
-
* and that the OLD `/{orgId}/agents` endpoints are NEVER hit.
|
|
11
|
-
* Run: node --test lib/org/cohort-client.test.mjs
|
|
12
|
-
*/
|
|
13
|
-
"use strict";
|
|
14
|
-
|
|
15
|
-
import { test } from "node:test";
|
|
16
|
-
import assert from "node:assert/strict";
|
|
17
|
-
|
|
18
|
-
import { isEnabled, publishEntry, heartbeat, fetchDirectory } from "./cohort-client.mjs";
|
|
19
|
-
import { PROTOCOL_VERSION } from "./protocol.mjs";
|
|
20
|
-
|
|
21
|
-
// Ambient COHORT_* creds would leak into header assertions; clear them so the
|
|
22
|
-
// tests are hermetic regardless of host env.
|
|
23
|
-
delete process.env.COHORT_API_TOKEN;
|
|
24
|
-
delete process.env.COHORT_TOKEN;
|
|
25
|
-
delete process.env.COHORT_API_KEY;
|
|
26
|
-
delete process.env.COHORT_ORG_ID;
|
|
27
|
-
|
|
28
|
-
const BASE = "https://org.cohortapp.com";
|
|
29
|
-
// New enablement contract: enabled + (base || apiUrl+orgId).
|
|
30
|
-
const ON = { org: { cohort: { enabled: true, base: BASE, token: "tok-xyz" } } };
|
|
31
|
-
const OFF = { org: { cohort: { enabled: false, base: BASE } } };
|
|
32
|
-
|
|
33
|
-
/** A fake fetch that records calls and returns a canned res frame. */
|
|
34
|
-
function fakeFetch(responder) {
|
|
35
|
-
const calls = [];
|
|
36
|
-
const impl = async (url, init) => {
|
|
37
|
-
calls.push({ url, init, headers: (init && init.headers) || {} });
|
|
38
|
-
const r = responder ? responder(url, init, calls.length - 1) : null;
|
|
39
|
-
const status = r && typeof r.status === "number" ? r.status : 200;
|
|
40
|
-
const ok = r && typeof r.ok === "boolean" ? r.ok : status >= 200 && status < 300;
|
|
41
|
-
const body = r ? r.body : { ok: true, result: null };
|
|
42
|
-
return { ok, status, headers: { get: () => undefined }, json: async () => body };
|
|
43
|
-
};
|
|
44
|
-
impl.calls = calls;
|
|
45
|
-
return impl;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
test("isEnabled: requires enabled + (base or apiUrl+orgId)", () => {
|
|
49
|
-
assert.equal(isEnabled(ON), true);
|
|
50
|
-
assert.equal(isEnabled(OFF), false);
|
|
51
|
-
assert.equal(isEnabled({ org: { cohort: { enabled: true, apiUrl: "x", orgId: "o" } } }), true);
|
|
52
|
-
assert.equal(isEnabled(null), false);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("publishEntry: no-op disabled; POSTs registry.register (NOT /agents) when enabled", async () => {
|
|
56
|
-
assert.deepEqual(await publishEntry({ cfg: OFF, entry: { id: "a" } }), { ok: false, skipped: true });
|
|
57
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: null } }));
|
|
58
|
-
const r = await publishEntry({ cfg: ON, entry: { id: "alex-ai", name: "Alex" }, fetchImpl: f });
|
|
59
|
-
assert.equal(r.ok, true);
|
|
60
|
-
assert.equal(f.calls[0].url, `${BASE}/api/v1/registry.register`);
|
|
61
|
-
assert.equal(f.calls[0].init.method, "POST");
|
|
62
|
-
assert.equal(f.calls[0].headers.authorization, "Bearer tok-xyz");
|
|
63
|
-
assert.equal(f.calls[0].headers["x-org-protocol"], String(PROTOCOL_VERSION));
|
|
64
|
-
assert.equal(f.calls[0].headers["x-idempotency-key"], "registry.register:alex-ai");
|
|
65
|
-
// the OLD /{orgId}/agents endpoint must NEVER be hit
|
|
66
|
-
assert.doesNotMatch(f.calls[0].url, /\/agents\//);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("publishEntry: trims a rich entry to the strict registerSchema (archetype object → string)", async () => {
|
|
70
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: null } }));
|
|
71
|
-
const entry = {
|
|
72
|
-
id: "robin-ai",
|
|
73
|
-
name: "Robin",
|
|
74
|
-
fullName: "Robin Vega",
|
|
75
|
-
archetype: { function: "engineering", altitude: "vp", label: "VP Eng" },
|
|
76
|
-
principal: { fullName: "Sam Human", title: "CEO" },
|
|
77
|
-
towers: [{ id: "t1", name: "Platform" }],
|
|
78
|
-
focus: [{ title: "ship the thing" }],
|
|
79
|
-
};
|
|
80
|
-
await publishEntry({ cfg: ON, entry, fetchImpl: f });
|
|
81
|
-
const sent = JSON.parse(f.calls[0].init.body);
|
|
82
|
-
assert.equal(sent.displayName, "Robin Vega");
|
|
83
|
-
assert.equal(sent.archetype, "VP Eng");
|
|
84
|
-
assert.equal(sent.humanSponsor, "Sam Human");
|
|
85
|
-
// the full rich entry rides under `card` (server chain-redacts to hasCard)
|
|
86
|
-
assert.equal(sent.card.id, "robin-ai");
|
|
87
|
-
assert.deepEqual(sent.card.archetype, entry.archetype);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("publishEntry: server error frame → {ok:false} (fail-open shape preserved)", async () => {
|
|
91
|
-
const f = fakeFetch(() => ({ ok: false, status: 500, body: { ok: false, error: { code: "INTERNAL", message: "boom" } } }));
|
|
92
|
-
const r = await publishEntry({ cfg: ON, entry: { id: "x" }, fetchImpl: f });
|
|
93
|
-
assert.equal(r.ok, false);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("heartbeat: no-op disabled; POSTs presence.beat (NOT /agents/{id}/status) when enabled", async () => {
|
|
97
|
-
assert.deepEqual(await heartbeat({ cfg: OFF, id: "a" }), { ok: false, skipped: true });
|
|
98
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { directives: [] } } }));
|
|
99
|
-
const r = await heartbeat({ cfg: ON, id: "alex-ai", status: "active", fetchImpl: f });
|
|
100
|
-
assert.equal(r.ok, true);
|
|
101
|
-
assert.equal(f.calls[0].url, `${BASE}/api/v1/presence.beat`);
|
|
102
|
-
assert.equal(f.calls[0].init.method, "POST");
|
|
103
|
-
assert.doesNotMatch(f.calls[0].url, /\/agents\//);
|
|
104
|
-
const sent = JSON.parse(f.calls[0].init.body);
|
|
105
|
-
assert.equal(sent.status.state, "active");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("fetchDirectory: [] disabled; parses array + {members:[]} + {agents:[]} shapes", async () => {
|
|
109
|
-
assert.deepEqual(await fetchDirectory({ cfg: OFF }), []);
|
|
110
|
-
const arr = await fetchDirectory({ cfg: ON, fetchImpl: fakeFetch(() => ({ body: [{ id: "x" }] })) });
|
|
111
|
-
assert.equal(arr.length, 1);
|
|
112
|
-
const members = await fetchDirectory({ cfg: ON, fetchImpl: fakeFetch(() => ({ body: { members: [{ id: "m1" }, { id: "m2" }] } })) });
|
|
113
|
-
assert.equal(members.length, 2);
|
|
114
|
-
const agents = await fetchDirectory({ cfg: ON, fetchImpl: fakeFetch(() => ({ body: { agents: [{ id: "y" }] } })) });
|
|
115
|
-
assert.equal(agents.length, 1);
|
|
116
|
-
// GET /v1/directory, never the old /{orgId}/agents
|
|
117
|
-
const f = fakeFetch(() => ({ body: { members: [] } }));
|
|
118
|
-
await fetchDirectory({ cfg: ON, fetchImpl: f });
|
|
119
|
-
assert.equal(f.calls[0].url, `${BASE}/api/v1/directory`);
|
|
120
|
-
assert.equal(f.calls[0].init.method, "GET");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("fetchDirectory: network error → [] (fail-open)", async () => {
|
|
124
|
-
const arr = await fetchDirectory({ cfg: ON, fetchImpl: async () => { throw new Error("network down"); } });
|
|
125
|
-
assert.deepEqual(arr, []);
|
|
126
|
-
});
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cost-sync.test.mjs — read today's router ledger and report rows to Cohort via
|
|
3
|
-
* cost.report, idempotently and batched, preserving the G16 attribution fields,
|
|
4
|
-
* reconciling with the local budget total. Tmp ledger dir + injected fetchImpl.
|
|
5
|
-
* Run: node --test lib/org/cost-sync.test.mjs
|
|
6
|
-
*/
|
|
7
|
-
"use strict";
|
|
8
|
-
|
|
9
|
-
import { test } from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import { mkdtempSync, mkdirSync, writeFileSync } from "node:fs";
|
|
12
|
-
import { tmpdir } from "node:os";
|
|
13
|
-
import { join } from "node:path";
|
|
14
|
-
|
|
15
|
-
import { readLedgerRows, rowId, toReportRow, reportLedger } from "./cost-sync.mjs";
|
|
16
|
-
|
|
17
|
-
const BASE = "https://org.cohortapp.com";
|
|
18
|
-
const CFG = { org: { cohort: { enabled: true, base: BASE, token: "tok", orgId: "alex-ai" } } };
|
|
19
|
-
const OFF = { org: { cohort: { enabled: false } } };
|
|
20
|
-
const DAY = "2026-06-11";
|
|
21
|
-
|
|
22
|
-
delete process.env.COHORT_API_TOKEN;
|
|
23
|
-
delete process.env.COHORT_TOKEN;
|
|
24
|
-
|
|
25
|
-
/** Write a JSONL ledger file for a day under a fresh tmp ledger dir. Returns dir. */
|
|
26
|
-
function tmpLedger(rows, day = DAY) {
|
|
27
|
-
const dir = mkdtempSync(join(tmpdir(), "cost-sync-"));
|
|
28
|
-
mkdirSync(dir, { recursive: true });
|
|
29
|
-
const body = rows.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
30
|
-
writeFileSync(join(dir, `${day}.jsonl`), body);
|
|
31
|
-
return dir;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function fakeFetch(responder) {
|
|
35
|
-
const calls = [];
|
|
36
|
-
const impl = async (url, init) => {
|
|
37
|
-
calls.push({ url, init, headers: (init && init.headers) || {}, body: init && init.body ? JSON.parse(init.body) : null });
|
|
38
|
-
const r = responder ? responder(url, init, calls.length - 1) : null;
|
|
39
|
-
const status = r && typeof r.status === "number" ? r.status : 200;
|
|
40
|
-
const ok = r && typeof r.ok === "boolean" ? r.ok : status >= 200 && status < 300;
|
|
41
|
-
const body = r ? r.body : { ok: true, result: null };
|
|
42
|
-
return { ok, status, headers: { get: () => undefined }, json: async () => body };
|
|
43
|
-
};
|
|
44
|
-
impl.calls = calls;
|
|
45
|
-
return impl;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const SAMPLE_ROWS = [
|
|
49
|
-
{ ts: `${DAY}T01:00:00.000Z`, agent: "alex", model: "claude-opus-4", input_tokens: 1000, output_tokens: 200, estimated_usd: 0.05, decision_id: "d-1", session_id: "s-1", source: "router", cadence: null },
|
|
50
|
-
{ ts: `${DAY}T02:00:00.000Z`, agent: "alex", model: "claude-haiku", input_tokens: 500, output_tokens: 50, estimated_usd: 0.001, session_id: "s-2", source: "cadence", lane: "org-sync" },
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
// --- read + normalise ------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
test("readLedgerRows: parses JSONL, skips blank/corrupt lines, [] when missing", () => {
|
|
56
|
-
const dir = tmpLedger(SAMPLE_ROWS);
|
|
57
|
-
const rows = readLedgerRows({ ledgerDir: dir, day: DAY });
|
|
58
|
-
assert.equal(rows.length, 2);
|
|
59
|
-
assert.equal(rows[0].decision_id, "d-1");
|
|
60
|
-
// missing file → []
|
|
61
|
-
assert.deepEqual(readLedgerRows({ ledgerDir: dir, day: "1999-01-01" }), []);
|
|
62
|
-
// corrupt line is skipped
|
|
63
|
-
const dir2 = mkdtempSync(join(tmpdir(), "cost-sync-corrupt-"));
|
|
64
|
-
writeFileSync(join(dir2, `${DAY}.jsonl`), '{"agent":"a","estimated_usd":1}\nnot json\n\n{"agent":"b","estimated_usd":2}\n');
|
|
65
|
-
assert.equal(readLedgerRows({ ledgerDir: dir2, day: DAY }).length, 2);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("rowId: stable identity (id > natural keys > content hash)", () => {
|
|
69
|
-
assert.equal(rowId({ id: "explicit" }), "explicit");
|
|
70
|
-
assert.equal(rowId({ decision_id: "d", session_id: "s", ts: "t" }), "d:s:t");
|
|
71
|
-
const h1 = rowId({ agent: "a", estimated_usd: 1 });
|
|
72
|
-
const h2 = rowId({ estimated_usd: 1, agent: "a" }); // key order independent
|
|
73
|
-
assert.equal(h1, h2);
|
|
74
|
-
assert.notEqual(rowId({ agent: "a" }), rowId({ agent: "b" }));
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("toReportRow: preserves the G16 attribution fields (source/cadence) + budget figure", () => {
|
|
78
|
-
const wire = toReportRow(SAMPLE_ROWS[1], { day: DAY });
|
|
79
|
-
assert.equal(wire.source, "cadence");
|
|
80
|
-
assert.equal(wire.cadence, "org-sync"); // falls back to `lane`
|
|
81
|
-
assert.equal(wire.estimated_usd, 0.001);
|
|
82
|
-
assert.equal(wire.day, DAY);
|
|
83
|
-
assert.ok(wire.id);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// --- reportLedger: idempotent batched push ---------------------------------
|
|
87
|
-
|
|
88
|
-
test("reportLedger: posts rows to cost.report, reconciles local USD total", async () => {
|
|
89
|
-
const dir = tmpLedger(SAMPLE_ROWS);
|
|
90
|
-
const f = fakeFetch((url) => (url.endsWith("/v1/cost.report") ? { body: { ok: true, result: { ingested: 2 } } } : null));
|
|
91
|
-
const out = await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, fetchImpl: f });
|
|
92
|
-
assert.equal(out.enabled, true);
|
|
93
|
-
assert.equal(out.rowsRead, 2);
|
|
94
|
-
assert.equal(out.reported, 2);
|
|
95
|
-
assert.equal(out.failed, 0);
|
|
96
|
-
// reconciles with budget-guard's view (sum of estimated_usd)
|
|
97
|
-
assert.equal(out.localUSD, 0.051);
|
|
98
|
-
// one batch (default batch size), routed to cost.report
|
|
99
|
-
const reportCalls = f.calls.filter((c) => c.url.endsWith("/v1/cost.report"));
|
|
100
|
-
assert.equal(reportCalls.length, 1);
|
|
101
|
-
assert.equal(reportCalls[0].body.rows.length, 2);
|
|
102
|
-
// each wire row carried its stable id + attribution
|
|
103
|
-
assert.ok(reportCalls[0].body.rows[0].id);
|
|
104
|
-
assert.equal(reportCalls[0].body.rows[1].source, "cadence");
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("reportLedger: idempotency key is stable across re-runs of the same rows", async () => {
|
|
108
|
-
const dir = tmpLedger(SAMPLE_ROWS);
|
|
109
|
-
const f1 = fakeFetch(() => ({ body: { ok: true, result: null } }));
|
|
110
|
-
const f2 = fakeFetch(() => ({ body: { ok: true, result: null } }));
|
|
111
|
-
await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, fetchImpl: f1 });
|
|
112
|
-
await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, fetchImpl: f2 });
|
|
113
|
-
const k1 = f1.calls.find((c) => c.url.endsWith("/v1/cost.report")).headers["x-idempotency-key"];
|
|
114
|
-
const k2 = f2.calls.find((c) => c.url.endsWith("/v1/cost.report")).headers["x-idempotency-key"];
|
|
115
|
-
assert.ok(k1);
|
|
116
|
-
assert.equal(k1, k2); // same rows → same key → server collapses the replay
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("reportLedger: batches by batchSize", async () => {
|
|
120
|
-
const many = Array.from({ length: 5 }, (_, i) => ({ id: `r-${i}`, agent: "a", estimated_usd: 0.01 }));
|
|
121
|
-
const dir = tmpLedger(many);
|
|
122
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: null } }));
|
|
123
|
-
const out = await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, batchSize: 2, fetchImpl: f });
|
|
124
|
-
assert.equal(out.reported, 5);
|
|
125
|
-
assert.equal(out.batches.length, 3); // 2 + 2 + 1
|
|
126
|
-
assert.deepEqual(out.batches.map((b) => b.count), [2, 2, 1]);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("reportLedger: disabled org → reports nothing but still reconciles local total", async () => {
|
|
130
|
-
const dir = tmpLedger(SAMPLE_ROWS);
|
|
131
|
-
const out = await reportLedger({ cfg: OFF, ledgerDir: dir, day: DAY });
|
|
132
|
-
assert.equal(out.enabled, false);
|
|
133
|
-
assert.equal(out.reported, 0);
|
|
134
|
-
assert.equal(out.localUSD, 0.051);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("reportLedger: server error → rows counted as failed, never throws", async () => {
|
|
138
|
-
const dir = tmpLedger(SAMPLE_ROWS);
|
|
139
|
-
const f = fakeFetch(() => ({ ok: false, status: 500, body: { ok: false, error: { code: "INTERNAL", message: "boom" } } }));
|
|
140
|
-
const out = await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, fetchImpl: f });
|
|
141
|
-
assert.equal(out.reported, 0);
|
|
142
|
-
assert.equal(out.failed, 2);
|
|
143
|
-
assert.equal(out.batches[0].code, "INTERNAL");
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test("reportLedger: empty ledger → no call, zero totals", async () => {
|
|
147
|
-
const dir = mkdtempSync(join(tmpdir(), "cost-sync-empty-"));
|
|
148
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: null } }));
|
|
149
|
-
const out = await reportLedger({ cfg: CFG, ledgerDir: dir, day: DAY, fetchImpl: f });
|
|
150
|
-
assert.equal(out.rowsRead, 0);
|
|
151
|
-
assert.equal(out.reported, 0);
|
|
152
|
-
assert.equal(f.calls.length, 0);
|
|
153
|
-
});
|
package/lib/org/doctor.test.mjs
DELETED
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* doctor.test.mjs — Cohort connectivity probes (lib/org/doctor.mjs).
|
|
3
|
-
* All five probe outcomes with a stubbed fetch: not-enrolled short-circuit,
|
|
4
|
-
* token-rejected fail, unreachable fail, drift-header warn/absent/aligned,
|
|
5
|
-
* paired-ok vs unpaired-FORBIDDEN_SCOPE, and the orgmail mailbox probes.
|
|
6
|
-
* Run: node --test lib/org/doctor.test.mjs
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
"use strict";
|
|
10
|
-
|
|
11
|
-
import { test } from "node:test";
|
|
12
|
-
import assert from "node:assert/strict";
|
|
13
|
-
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
14
|
-
import { tmpdir } from "node:os";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
|
|
17
|
-
import { checkOrgConnectivity, checkReactiveLaneWiring } from "./doctor.mjs";
|
|
18
|
-
import { PROTOCOL_VERSION } from "./protocol.mjs";
|
|
19
|
-
|
|
20
|
-
// Ambient COHORT_* vars must not leak in.
|
|
21
|
-
for (const k of ["COHORT_API_TOKEN", "COHORT_TOKEN", "COHORT_API_KEY", "COHORT_ORG_ID", "COHORT_BASE", "COHORT_API_URL", "COHORT_AGENT_ID", "COHORT_AGENT_EMAIL"]) delete process.env[k];
|
|
22
|
-
|
|
23
|
-
function root({ enrolled = true, orgmail = false } = {}) {
|
|
24
|
-
const r = mkdtempSync(join(tmpdir(), "org-doctor-"));
|
|
25
|
-
mkdirSync(join(r, "config"), { recursive: true });
|
|
26
|
-
if (enrolled) {
|
|
27
|
-
writeFileSync(
|
|
28
|
-
join(r, "config", "org.yaml"),
|
|
29
|
-
["org:", " cohort:", " enabled: true", " base: https://org.example", " orgId: acme", " token: tok-doc"].join("\n"),
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
if (orgmail) writeFileSync(join(r, "config", "orgmail.yaml"), "orgmail:\n enabled: true\n");
|
|
33
|
-
return r;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* A routed fetch stub. `routes` maps a URL suffix to a responder
|
|
38
|
-
* ({status, body, headers}) or a function of (url, init).
|
|
39
|
-
*/
|
|
40
|
-
function stubFetch(routes) {
|
|
41
|
-
const calls = [];
|
|
42
|
-
const fn = async (url, init) => {
|
|
43
|
-
const u = String(url);
|
|
44
|
-
calls.push({ url: u, init: init || {} });
|
|
45
|
-
for (const [suffix, r] of Object.entries(routes)) {
|
|
46
|
-
if (u.endsWith(suffix)) {
|
|
47
|
-
const spec = typeof r === "function" ? r(u, init) : r;
|
|
48
|
-
if (spec === "throw") throw new Error("network down");
|
|
49
|
-
const status = spec.status ?? 200;
|
|
50
|
-
return {
|
|
51
|
-
ok: status >= 200 && status < 300,
|
|
52
|
-
status,
|
|
53
|
-
headers: { get: (k) => (spec.headers ? spec.headers[k.toLowerCase()] : undefined) },
|
|
54
|
-
json: async () => spec.body ?? {},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
throw new Error(`unrouted ${u}`);
|
|
59
|
-
};
|
|
60
|
-
fn.calls = calls;
|
|
61
|
-
return fn;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const DIR_OK = { status: 200, body: { members: [] }, headers: { "x-org-protocol": String(PROTOCOL_VERSION) } };
|
|
65
|
-
const HISTORY_OK = { status: 200, body: { ok: true, result: { messages: [] } } };
|
|
66
|
-
const HISTORY_BAD_PARAMS = { status: 400, body: { ok: false, error: { code: "BAD_REQUEST", message: "channelId: Required" } } };
|
|
67
|
-
const CHANNELS_OK = { status: 200, body: { ok: true, result: { channels: [{ id: "c1" }, { id: "c2" }] } } };
|
|
68
|
-
|
|
69
|
-
const levelsOf = (rs) => rs.map((r) => r.level);
|
|
70
|
-
const msgs = (rs) => rs.map((r) => r.msg).join("\n");
|
|
71
|
-
|
|
72
|
-
test("probe 1: not enrolled → single ok line, no network", async () => {
|
|
73
|
-
const r = root({ enrolled: false });
|
|
74
|
-
const f = stubFetch({});
|
|
75
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
76
|
-
assert.equal(rs.length, 1);
|
|
77
|
-
assert.equal(rs[0].level, "ok");
|
|
78
|
-
assert.match(rs[0].msg, /not enrolled/);
|
|
79
|
-
assert.equal(f.calls.length, 0);
|
|
80
|
-
rmSync(r, { recursive: true, force: true });
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("probe 2: happy path — reachable + latency + aligned protocol header + paired", async () => {
|
|
84
|
-
const r = root();
|
|
85
|
-
const f = stubFetch({
|
|
86
|
-
"/api/v1/directory": DIR_OK,
|
|
87
|
-
"/api/v1/messaging.channels": CHANNELS_OK,
|
|
88
|
-
"/api/v1/messaging.history": HISTORY_OK,
|
|
89
|
-
});
|
|
90
|
-
let t = 1000;
|
|
91
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {}, now: () => (t += 40) });
|
|
92
|
-
// …reachable, protocol, paired, INBOUND PATH, daemon-visible org config.
|
|
93
|
-
assert.deepEqual(levelsOf(rs), ["ok", "ok", "ok", "ok", "ok", "ok"]);
|
|
94
|
-
assert.match(msgs(rs), /inbound path works/);
|
|
95
|
-
assert.match(msgs(rs), /visible to the daemon cadences/);
|
|
96
|
-
assert.match(msgs(rs), /directory \d+ms/);
|
|
97
|
-
assert.match(msgs(rs), /protocol version aligned/);
|
|
98
|
-
assert.match(msgs(rs), /paired to a workforce member \(2 channel\(s\)\)/);
|
|
99
|
-
rmSync(r, { recursive: true, force: true });
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("probe 2: 401 → FAIL token rejected, remaining probes skipped", async () => {
|
|
103
|
-
const r = root();
|
|
104
|
-
const f = stubFetch({ "/api/v1/directory": { status: 401, body: {} } });
|
|
105
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
106
|
-
assert.equal(rs.at(-1).level, "fail");
|
|
107
|
-
assert.match(rs.at(-1).msg, /token rejected .*Settings → API keys/i);
|
|
108
|
-
assert.equal(f.calls.length, 1, "no further probes after a rejected token");
|
|
109
|
-
rmSync(r, { recursive: true, force: true });
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("probe 2: unreachable → FAIL (enrolled agent that cannot reach its org is broken)", async () => {
|
|
113
|
-
const r = root();
|
|
114
|
-
const f = stubFetch({ "/api/v1/directory": "throw" });
|
|
115
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
116
|
-
assert.equal(rs.at(-1).level, "fail");
|
|
117
|
-
assert.match(rs.at(-1).msg, /unreachable/);
|
|
118
|
-
rmSync(r, { recursive: true, force: true });
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("probe 2 drift: header present ≠ local → warn `cohort upgrade`", async () => {
|
|
122
|
-
const r = root();
|
|
123
|
-
const f = stubFetch({
|
|
124
|
-
"/api/v1/directory": { status: 200, body: {}, headers: { "x-org-protocol": "99" } },
|
|
125
|
-
"/api/v1/messaging.channels": CHANNELS_OK,
|
|
126
|
-
});
|
|
127
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
128
|
-
const drift = rs.find((x) => /protocol drift/.test(x.msg));
|
|
129
|
-
assert.ok(drift, "drift line present");
|
|
130
|
-
assert.equal(drift.level, "warn");
|
|
131
|
-
assert.match(drift.msg, /99 ≠ local 1/);
|
|
132
|
-
assert.match(drift.msg, /cohort upgrade/);
|
|
133
|
-
rmSync(r, { recursive: true, force: true });
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test("probe 2 drift: header ABSENT → warn hq-predates-phase-1", async () => {
|
|
137
|
-
const r = root();
|
|
138
|
-
const f = stubFetch({
|
|
139
|
-
"/api/v1/directory": { status: 200, body: {} }, // no headers at all
|
|
140
|
-
"/api/v1/messaging.channels": CHANNELS_OK,
|
|
141
|
-
});
|
|
142
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
143
|
-
const absent = rs.find((x) => /does not report protocol version/.test(x.msg));
|
|
144
|
-
assert.ok(absent);
|
|
145
|
-
assert.equal(absent.level, "warn");
|
|
146
|
-
rmSync(r, { recursive: true, force: true });
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("probe 3: FORBIDDEN_SCOPE from messaging.channels → FAIL not-paired", async () => {
|
|
150
|
-
const r = root();
|
|
151
|
-
const f = stubFetch({
|
|
152
|
-
"/api/v1/directory": DIR_OK,
|
|
153
|
-
"/api/v1/messaging.channels": { status: 403, body: { ok: false, error: { code: "FORBIDDEN_SCOPE", message: "not paired to a workforce member" } } },
|
|
154
|
-
});
|
|
155
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
156
|
-
const pair = rs.find((x) => /not paired/.test(x.msg));
|
|
157
|
-
assert.ok(pair);
|
|
158
|
-
assert.equal(pair.level, "fail");
|
|
159
|
-
assert.match(pair.msg, /cohort pair/);
|
|
160
|
-
rmSync(r, { recursive: true, force: true });
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("probe 3 garnish: identity hint appends the member slug; hint failure never degrades", async () => {
|
|
164
|
-
const r = root();
|
|
165
|
-
const f = stubFetch({
|
|
166
|
-
"/api/v1/directory": DIR_OK,
|
|
167
|
-
"/api/v1/messaging.channels": CHANNELS_OK,
|
|
168
|
-
"/api/v1/org/members/astra?orgId=acme": { status: 200, body: { slug: "astra", displayName: "Astra" } },
|
|
169
|
-
});
|
|
170
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: { COHORT_AGENT_ID: "astra" } });
|
|
171
|
-
const paired = rs.find((x) => /paired to a workforce member/.test(x.msg));
|
|
172
|
-
assert.match(paired.msg, /member astra/);
|
|
173
|
-
// hint present but whoami fails → still ok, no garnish
|
|
174
|
-
const f2 = stubFetch({
|
|
175
|
-
"/api/v1/directory": DIR_OK,
|
|
176
|
-
"/api/v1/messaging.channels": CHANNELS_OK,
|
|
177
|
-
"/api/v1/org/members/astra?orgId=acme": { status: 404, body: {} },
|
|
178
|
-
});
|
|
179
|
-
const rs2 = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f2, env: { COHORT_AGENT_ID: "astra" } });
|
|
180
|
-
const paired2 = rs2.find((x) => /paired to a workforce member/.test(x.msg));
|
|
181
|
-
assert.equal(paired2.level, "ok");
|
|
182
|
-
assert.ok(!/member astra/.test(paired2.msg));
|
|
183
|
-
rmSync(r, { recursive: true, force: true });
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("probe 4: orgmail gate + family vendored → mailbox probe surfaces the address", async () => {
|
|
187
|
-
const r = root({ orgmail: true });
|
|
188
|
-
const f = stubFetch({
|
|
189
|
-
"/api/v1/directory": DIR_OK,
|
|
190
|
-
"/api/v1/messaging.channels": CHANNELS_OK,
|
|
191
|
-
"/api/v1/email.inbox": { status: 200, body: { ok: true, result: { mailbox: { address: "astra@agents.example" }, messages: [] } } },
|
|
192
|
-
});
|
|
193
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
194
|
-
const mail = rs.find((x) => /workspace mailbox/.test(x.msg));
|
|
195
|
-
assert.equal(mail.level, "ok");
|
|
196
|
-
assert.match(mail.msg, /astra@agents\.example/);
|
|
197
|
-
rmSync(r, { recursive: true, force: true });
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test("probe 4: NO mailbox FAILS and names who does what (it is a dead lane, not a degraded one)", async () => {
|
|
201
|
-
const r = root({ orgmail: true });
|
|
202
|
-
const f = stubFetch({
|
|
203
|
-
"/api/v1/directory": DIR_OK,
|
|
204
|
-
"/api/v1/messaging.channels": CHANNELS_OK,
|
|
205
|
-
"/api/v1/email.inbox": { status: 404, body: { ok: false, error: { code: "NOT_FOUND", message: "no mailbox for member" } } },
|
|
206
|
-
});
|
|
207
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
208
|
-
const mail = rs.find((x) => /Workspace mailbox/.test(x.msg));
|
|
209
|
-
assert.equal(mail.level, "fail");
|
|
210
|
-
// The whole point: an ACTOR and an ACT, not just a state.
|
|
211
|
-
assert.match(mail.msg, /workspace ADMIN/);
|
|
212
|
-
assert.match(mail.msg, /Settings → Email → Mailboxes/);
|
|
213
|
-
assert.match(mail.msg, /Settings → Email → Domains/);
|
|
214
|
-
// The server's own words survive, so the operator can tell the two apart.
|
|
215
|
-
assert.match(mail.msg, /no mailbox for member/);
|
|
216
|
-
rmSync(r, { recursive: true, force: true });
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("probe 4: an unpaired/unscoped key is a DIFFERENT fail with a different remedy", async () => {
|
|
220
|
-
const r = root({ orgmail: true });
|
|
221
|
-
const f = stubFetch({
|
|
222
|
-
"/api/v1/directory": DIR_OK,
|
|
223
|
-
"/api/v1/messaging.channels": CHANNELS_OK,
|
|
224
|
-
"/api/v1/email.inbox": { status: 403, body: { ok: false, error: { code: "FORBIDDEN_SCOPE", message: "not paired" } } },
|
|
225
|
-
});
|
|
226
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
227
|
-
const mail = rs.find((x) => /Workspace mailbox/.test(x.msg));
|
|
228
|
-
assert.equal(mail.level, "fail");
|
|
229
|
-
assert.match(mail.msg, /Settings → API keys/);
|
|
230
|
-
assert.ok(!/Settings → Email → Mailboxes/.test(mail.msg), "wrong remedy for this cause");
|
|
231
|
-
rmSync(r, { recursive: true, force: true });
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test("probe 4: an unknown error stays a WARN (fail-open on the unclassified)", async () => {
|
|
235
|
-
const r = root({ orgmail: true });
|
|
236
|
-
const f = stubFetch({
|
|
237
|
-
"/api/v1/directory": DIR_OK,
|
|
238
|
-
"/api/v1/messaging.channels": CHANNELS_OK,
|
|
239
|
-
"/api/v1/email.inbox": { status: 500, body: { ok: false, error: { code: "INTERNAL", message: "boom" } } },
|
|
240
|
-
});
|
|
241
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
242
|
-
const mail = rs.find((x) => /mailbox probe inconclusive/.test(x.msg));
|
|
243
|
-
assert.equal(mail.level, "warn");
|
|
244
|
-
rmSync(r, { recursive: true, force: true });
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test("no orgmail gate file → no mailbox probe at all", async () => {
|
|
248
|
-
const r = root();
|
|
249
|
-
const f = stubFetch({ "/api/v1/directory": DIR_OK, "/api/v1/messaging.channels": CHANNELS_OK });
|
|
250
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} });
|
|
251
|
-
assert.ok(!rs.some((x) => /mailbox/.test(x.msg)));
|
|
252
|
-
assert.ok(!f.calls.some((c) => c.url.includes("email.inbox")));
|
|
253
|
-
rmSync(r, { recursive: true, force: true });
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test("inbound probe FAILS loudly when the server rejects this SDK's params", () => {
|
|
257
|
-
// The real incident: fetchHistory sent `channel`, hq validates `channelId`,
|
|
258
|
-
// so the agent could not read a single message sent to it — while every other
|
|
259
|
-
// doctor line stayed green. Protocol drift must be a FAIL, not a shrug.
|
|
260
|
-
const r = root();
|
|
261
|
-
const f = stubFetch({
|
|
262
|
-
"/api/v1/directory": DIR_OK,
|
|
263
|
-
"/api/v1/messaging.channels": CHANNELS_OK,
|
|
264
|
-
"/api/v1/messaging.history": HISTORY_BAD_PARAMS,
|
|
265
|
-
});
|
|
266
|
-
return checkOrgConnectivity({ agentRoot: r, fetchImpl: f, env: {} }).then((rs) => {
|
|
267
|
-
const bad = rs.find((x) => /inbound path BROKEN/.test(x.msg));
|
|
268
|
-
assert.ok(bad, "a rejected inbound probe must surface");
|
|
269
|
-
assert.equal(bad.level, "fail");
|
|
270
|
-
assert.match(bad.msg, /protocol drift/);
|
|
271
|
-
assert.match(bad.msg, /upgrade/);
|
|
272
|
-
rmSync(r, { recursive: true, force: true });
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
/* ── probe 0: reactive-lane wiring (local, no network) ─────────────────────
|
|
277
|
-
* REGRESSION — observed live 2026-08-11 on a real seat. The agent repo carried
|
|
278
|
-
* a CURRENT lib/execution/ and a STALE scripts/daemon/agent-daemon.mjs, so
|
|
279
|
-
* `maestro-daemon.mjs` (which resolves ./agent-daemon.mjs from the agent's own
|
|
280
|
-
* dir) ran a build with no `runExecutionLadder` call. The wide inbound was
|
|
281
|
-
* current to within one ledger row and every event was still answered, so the
|
|
282
|
-
* agent looked healthy — while intake / obligation-match / rung / journal never
|
|
283
|
-
* ran at all and state/execution/journal.jsonl stayed empty. Nothing detected
|
|
284
|
-
* it, because the new build cannot warn about the old one.
|
|
285
|
-
*/
|
|
286
|
-
|
|
287
|
-
function laneRoot({ daemon, pipeline = true }) {
|
|
288
|
-
const r = mkdtempSync(join(tmpdir(), "org-doctor-lane-"));
|
|
289
|
-
if (pipeline) {
|
|
290
|
-
mkdirSync(join(r, "lib", "execution"), { recursive: true });
|
|
291
|
-
writeFileSync(join(r, "lib", "execution", "pipeline.mjs"), "export const processOne = () => {};\n");
|
|
292
|
-
}
|
|
293
|
-
if (daemon !== undefined) {
|
|
294
|
-
mkdirSync(join(r, "scripts", "daemon"), { recursive: true });
|
|
295
|
-
writeFileSync(join(r, "scripts", "daemon", "agent-daemon.mjs"), daemon);
|
|
296
|
-
}
|
|
297
|
-
return r;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
test("reactive-lane probe FAILS when the daemon copy predates the execution ladder", () => {
|
|
301
|
-
const r = laneRoot({ daemon: "async function processItemTraced(){ return classifyItem(item); }\n" });
|
|
302
|
-
const v = checkReactiveLaneWiring(r);
|
|
303
|
-
assert.equal(v.level, "fail");
|
|
304
|
-
assert.match(v.msg, /never calls runExecutionLadder/);
|
|
305
|
-
assert.match(v.msg, /journal\.jsonl will stay empty/);
|
|
306
|
-
assert.match(v.msg, /upgrade/, "must tell the operator what to DO");
|
|
307
|
-
rmSync(r, { recursive: true, force: true });
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
test("reactive-lane probe is OK when the daemon does call the ladder", () => {
|
|
311
|
-
const r = laneRoot({ daemon: "const gate = await runExecutionLadder(item, service, itemId, trace_id, deps);\n" });
|
|
312
|
-
const v = checkReactiveLaneWiring(r);
|
|
313
|
-
assert.equal(v.level, "ok");
|
|
314
|
-
rmSync(r, { recursive: true, force: true });
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
test("reactive-lane probe stays silent when the repo is not an agent layout", () => {
|
|
318
|
-
// No lib/execution → the framework is not vendored here; nothing to compare.
|
|
319
|
-
const r = laneRoot({ daemon: "whatever\n", pipeline: false });
|
|
320
|
-
assert.equal(checkReactiveLaneWiring(r), null);
|
|
321
|
-
// …and no daemon at all is equally not-applicable.
|
|
322
|
-
const r2 = laneRoot({});
|
|
323
|
-
assert.equal(checkReactiveLaneWiring(r2), null);
|
|
324
|
-
rmSync(r, { recursive: true, force: true });
|
|
325
|
-
rmSync(r2, { recursive: true, force: true });
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
test("reactive-lane probe fails OPEN (warn, never throw) on an unreadable daemon", () => {
|
|
329
|
-
const r = laneRoot({ daemon: "runExecutionLadder\n" });
|
|
330
|
-
const v = checkReactiveLaneWiring(r, {
|
|
331
|
-
existsSync: () => true,
|
|
332
|
-
readFileSync: () => { throw new Error("EACCES"); },
|
|
333
|
-
});
|
|
334
|
-
assert.equal(v.level, "warn");
|
|
335
|
-
assert.match(v.msg, /EACCES/, "the cause must be named, not swallowed");
|
|
336
|
-
rmSync(r, { recursive: true, force: true });
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
test("checkOrgConnectivity surfaces the lane probe even for an un-enrolled agent", async () => {
|
|
340
|
-
const r = laneRoot({ daemon: "processItemTraced\n" });
|
|
341
|
-
const rs = await checkOrgConnectivity({ agentRoot: r, env: {}, fetchImpl: () => { throw new Error("no network"); } });
|
|
342
|
-
const lane = rs.find((x) => /Reactive lane/.test(x.msg));
|
|
343
|
-
assert.ok(lane, "the lane probe must run before the not-enrolled short-circuit");
|
|
344
|
-
assert.equal(lane.level, "fail");
|
|
345
|
-
rmSync(r, { recursive: true, force: true });
|
|
346
|
-
});
|