@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,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* write.test.mjs — the effect edge of the design sync (WP-M7 mechanic 4).
|
|
3
|
-
*
|
|
4
|
-
* Real files in a temp dir, not stubs: the claims worth holding here are about
|
|
5
|
-
* the FILESYSTEM — that an unchanged foundation touches no mtime, and that the
|
|
6
|
-
* two markdown files land at 0644 so any tool on the seat can read them.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { test } from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
import { chmodSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, writeFileSync, statSync, utimesSync, existsSync, rmSync } from "node:fs";
|
|
12
|
-
import { join, resolve } from "node:path";
|
|
13
|
-
import { tmpdir } from "node:os";
|
|
14
|
-
|
|
15
|
-
import { syncDesign, writeDesignFiles, writeFoundationState, readFoundationState, filesMatch, workspaceName, DESIGN_FILE_MODE } from "./write.mjs";
|
|
16
|
-
import { renderDesignBundle } from "./design-md.mjs";
|
|
17
|
-
import { DESIGN_STATE_REL } from "./refresh-gate.mjs";
|
|
18
|
-
|
|
19
|
-
const NOW = Date.parse("2026-09-08T12:00:00Z");
|
|
20
|
-
const FIXTURE = JSON.parse(readFileSync(new URL("./fixtures/foundation.json", import.meta.url), "utf8"));
|
|
21
|
-
|
|
22
|
-
/** A `branding.getFoundation` result frame's `result`. */
|
|
23
|
-
const result = (over = {}) => ({
|
|
24
|
-
foundation: FIXTURE,
|
|
25
|
-
version: { id: "bv_01HQZ", seq: 7, label: "V2.7" },
|
|
26
|
-
headVersionId: "bv_01HQZ",
|
|
27
|
-
isHead: true,
|
|
28
|
-
unversioned: false,
|
|
29
|
-
history: [],
|
|
30
|
-
...over,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
function tmpRoot(t) {
|
|
34
|
-
const dir = mkdtempSync(join(tmpdir(), "maestro-design-write-"));
|
|
35
|
-
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
36
|
-
return dir;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
test("a first sync writes both files plus the state snapshot", (t) => {
|
|
40
|
-
const root = tmpRoot(t);
|
|
41
|
-
const out = syncDesign({ agentRoot: root, result: result(), now: NOW, name: "Northwind" });
|
|
42
|
-
|
|
43
|
-
assert.deepEqual(out.written, ["DESIGN.md", "PRODUCT.md"]);
|
|
44
|
-
assert.deepEqual(out.unchanged, []);
|
|
45
|
-
assert.equal(out.version, "bv_01HQZ");
|
|
46
|
-
assert.equal(out.outDir, root);
|
|
47
|
-
|
|
48
|
-
const design = readFileSync(join(root, "DESIGN.md"), "utf8");
|
|
49
|
-
assert.match(design, /^---\nname: "Northwind"\n/);
|
|
50
|
-
assert.match(design, /\{colors\.primary\}/);
|
|
51
|
-
assert.match(readFileSync(join(root, "PRODUCT.md"), "utf8"), /# Northwind — product voice/);
|
|
52
|
-
|
|
53
|
-
const state = JSON.parse(readFileSync(join(root, DESIGN_STATE_REL), "utf8"));
|
|
54
|
-
assert.equal(state.version, "bv_01HQZ");
|
|
55
|
-
assert.equal(state.versionLabel, "V2.7");
|
|
56
|
-
assert.equal(state.fetchedAt, "2026-09-08T12:00:00.000Z");
|
|
57
|
-
assert.deepEqual(state.foundation, FIXTURE, "the raw foundation is kept verbatim for the next gate read");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("both markdown files are 0644", (t) => {
|
|
61
|
-
const root = tmpRoot(t);
|
|
62
|
-
syncDesign({ agentRoot: root, result: result(), now: NOW, name: "Northwind" });
|
|
63
|
-
for (const rel of ["DESIGN.md", "PRODUCT.md"]) {
|
|
64
|
-
assert.equal(statSync(join(root, rel)).mode & 0o777, DESIGN_FILE_MODE, rel);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("re-syncing an unchanged foundation rewrites NOTHING — mtimes are untouched", (t) => {
|
|
69
|
-
const root = tmpRoot(t);
|
|
70
|
-
syncDesign({ agentRoot: root, result: result(), now: NOW, name: "Northwind" });
|
|
71
|
-
|
|
72
|
-
// Backdate both files so any rewrite is unmistakable.
|
|
73
|
-
const old = new Date(NOW - 86_400_000);
|
|
74
|
-
for (const rel of ["DESIGN.md", "PRODUCT.md"]) utimesSync(join(root, rel), old, old);
|
|
75
|
-
const before = ["DESIGN.md", "PRODUCT.md"].map((rel) => statSync(join(root, rel)).mtimeMs);
|
|
76
|
-
|
|
77
|
-
// A LATER clock, deliberately: the timestamp inside the file must not be what
|
|
78
|
-
// makes a sync a rewrite, or the interval poll would churn the tree forever.
|
|
79
|
-
const again = syncDesign({ agentRoot: root, result: result(), now: NOW + 20 * 60_000, name: "Northwind" });
|
|
80
|
-
assert.deepEqual(again.written, [], "nothing written");
|
|
81
|
-
assert.deepEqual(again.unchanged, ["DESIGN.md", "PRODUCT.md"]);
|
|
82
|
-
|
|
83
|
-
const after = ["DESIGN.md", "PRODUCT.md"].map((rel) => statSync(join(root, rel)).mtimeMs);
|
|
84
|
-
assert.deepEqual(after, before, "the files were not touched at all");
|
|
85
|
-
|
|
86
|
-
const design = readFileSync(join(root, "DESIGN.md"), "utf8");
|
|
87
|
-
assert.match(design, /\*\*Synced:\*\* 2026-09-08T12:00:00\.000Z/, "the stamp still says when the CONTENT was minted");
|
|
88
|
-
|
|
89
|
-
// The snapshot is still refreshed, so the gate knows the seat did check.
|
|
90
|
-
const state = JSON.parse(readFileSync(join(root, DESIGN_STATE_REL), "utf8"));
|
|
91
|
-
assert.equal(state.fetchedAt, "2026-09-08T12:20:00.000Z");
|
|
92
|
-
assert.equal(state.renderedAt, "2026-09-08T12:00:00.000Z");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("a changed foundation rewrites the affected file and re-stamps it", (t) => {
|
|
96
|
-
const root = tmpRoot(t);
|
|
97
|
-
syncDesign({ agentRoot: root, result: result(), now: NOW, name: "Northwind" });
|
|
98
|
-
|
|
99
|
-
const moved = JSON.parse(JSON.stringify(FIXTURE));
|
|
100
|
-
moved.colors[2].value = "#111111";
|
|
101
|
-
const out = syncDesign({
|
|
102
|
-
agentRoot: root,
|
|
103
|
-
result: result({ foundation: moved, headVersionId: "bv_02", version: { id: "bv_02", seq: 8, label: "V2.8" } }),
|
|
104
|
-
now: NOW + 20 * 60_000,
|
|
105
|
-
name: "Northwind",
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
assert.ok(out.written.includes("DESIGN.md"));
|
|
109
|
-
assert.equal(out.version, "bv_02");
|
|
110
|
-
const design = readFileSync(join(root, "DESIGN.md"), "utf8");
|
|
111
|
-
assert.match(design, /primary-moss: "#111111"/);
|
|
112
|
-
assert.match(design, /\*\*Synced:\*\* 2026-09-08T12:20:00\.000Z/);
|
|
113
|
-
assert.match(design, /\*\*Version:\*\* V2\.8/);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("--out sends the docs elsewhere; the snapshot always stays under the agent root", (t) => {
|
|
117
|
-
const root = tmpRoot(t);
|
|
118
|
-
const outDir = join(root, "docs", "brand");
|
|
119
|
-
const out = syncDesign({ agentRoot: root, outDir, result: result(), now: NOW, name: "Northwind" });
|
|
120
|
-
|
|
121
|
-
assert.equal(out.outDir, outDir);
|
|
122
|
-
assert.ok(existsSync(join(outDir, "DESIGN.md")));
|
|
123
|
-
assert.ok(!existsSync(join(root, "DESIGN.md")));
|
|
124
|
-
assert.ok(existsSync(join(root, DESIGN_STATE_REL)));
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("writeDesignFiles skips a file whose bytes already match and rewrites an unreadable one", (t) => {
|
|
128
|
-
const root = tmpRoot(t);
|
|
129
|
-
const files = renderDesignBundle(FIXTURE, { name: "Northwind" });
|
|
130
|
-
assert.deepEqual(writeDesignFiles({ files, outDir: root }).written, ["DESIGN.md", "PRODUCT.md"]);
|
|
131
|
-
assert.deepEqual(writeDesignFiles({ files, outDir: root }), { written: [], unchanged: ["DESIGN.md", "PRODUCT.md"] });
|
|
132
|
-
|
|
133
|
-
writeFileSync(join(root, "DESIGN.md"), "someone edited this by hand\n");
|
|
134
|
-
assert.deepEqual(writeDesignFiles({ files, outDir: root }).written, ["DESIGN.md"], "a hand edit is overwritten — the file is generated");
|
|
135
|
-
|
|
136
|
-
const boom = writeDesignFiles({ files, outDir: root }, { readFileImpl: () => { throw new Error("EIO"); } });
|
|
137
|
-
assert.deepEqual(boom.written, ["DESIGN.md", "PRODUCT.md"], "unreadable is treated as absent");
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("writeDesignFiles ignores malformed entries rather than throwing", (t) => {
|
|
141
|
-
const root = tmpRoot(t);
|
|
142
|
-
const out = writeDesignFiles({ files: [null, { rel: "A.md" }, { content: "x" }, { rel: "ok.md", content: "y" }], outDir: root });
|
|
143
|
-
assert.deepEqual(out.written, ["ok.md"]);
|
|
144
|
-
assert.deepEqual(writeDesignFiles({ files: "not a list", outDir: root }), { written: [], unchanged: [] });
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("readFoundationState reads back what writeFoundationState wrote; corrupt reads as never-synced", (t) => {
|
|
148
|
-
const root = tmpRoot(t);
|
|
149
|
-
assert.equal(readFoundationState(root), null, "absent → null");
|
|
150
|
-
|
|
151
|
-
const path = writeFoundationState(root, { fetchedAt: "2026-09-08T12:00:00.000Z", version: "bv_1", foundation: { colors: [] } });
|
|
152
|
-
assert.equal(path, join(root, DESIGN_STATE_REL));
|
|
153
|
-
assert.equal(readFoundationState(root).version, "bv_1");
|
|
154
|
-
|
|
155
|
-
writeFileSync(path, "{ not json");
|
|
156
|
-
assert.equal(readFoundationState(root), null, "corrupt → null → re-sync, never a wedge");
|
|
157
|
-
|
|
158
|
-
writeFileSync(path, JSON.stringify([1, 2]));
|
|
159
|
-
assert.equal(readFoundationState(root), null, "an array is not a snapshot");
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test("filesMatch is the idempotence probe the CLI's claim rests on", (t) => {
|
|
163
|
-
const root = tmpRoot(t);
|
|
164
|
-
const files = renderDesignBundle(FIXTURE, { name: "Northwind" });
|
|
165
|
-
assert.equal(filesMatch(files, root), false, "nothing on disk yet");
|
|
166
|
-
writeDesignFiles({ files, outDir: root });
|
|
167
|
-
assert.equal(filesMatch(files, root), true);
|
|
168
|
-
writeFileSync(join(root, "PRODUCT.md"), "drift\n");
|
|
169
|
-
assert.equal(filesMatch(files, root), false);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("workspaceName reads the org config and invents nothing", () => {
|
|
173
|
-
assert.equal(workspaceName({ org: { cohort: { orgName: "Northwind" } } }), "Northwind");
|
|
174
|
-
assert.equal(workspaceName({ org: { cohort: { workspace: "adaptic-hq" } } }), "adaptic-hq");
|
|
175
|
-
assert.equal(workspaceName({ org: { cohort: { orgName: " " } } }), "Workspace");
|
|
176
|
-
assert.equal(workspaceName({}), "Workspace");
|
|
177
|
-
assert.equal(workspaceName(null), "Workspace");
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("a state directory that does not exist yet is created", (t) => {
|
|
181
|
-
const root = join(tmpRoot(t), "not", "made", "yet");
|
|
182
|
-
mkdirSync(root, { recursive: true });
|
|
183
|
-
const out = syncDesign({ agentRoot: root, result: result(), now: NOW, name: "Northwind" });
|
|
184
|
-
assert.ok(existsSync(out.statePath));
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// ── two output directories, one state file ──────────────────────────────────
|
|
188
|
-
|
|
189
|
-
test("the daemon's agent dir and a --out directory do not invalidate each other, ever", (t) => {
|
|
190
|
-
// The scenario: an operator runs `maestro design sync --out docs/brand`
|
|
191
|
-
// (advertised in the guide) on a seat whose daemon polls the agent dir every
|
|
192
|
-
// 15 minutes. With one shared render stamp, each lane re-stamped the state
|
|
193
|
-
// and the other lane then found its own files "stale" — both files rewritten
|
|
194
|
-
// on every tick, forever, with a foundation that never changed.
|
|
195
|
-
const root = tmpRoot(t);
|
|
196
|
-
const brand = join(root, "docs", "brand");
|
|
197
|
-
let now = NOW;
|
|
198
|
-
const daemon = () => syncDesign({ agentRoot: root, result: result(), now: (now += 900_000), name: "Northwind" });
|
|
199
|
-
const cli = () => syncDesign({ agentRoot: root, outDir: brand, result: result(), now: (now += 60_000), name: "Northwind" });
|
|
200
|
-
|
|
201
|
-
assert.deepEqual(daemon().written, ["DESIGN.md", "PRODUCT.md"], "first write of the agent dir");
|
|
202
|
-
assert.deepEqual(cli().written, ["DESIGN.md", "PRODUCT.md"], "first write of docs/brand");
|
|
203
|
-
|
|
204
|
-
for (let i = 0; i < 6; i++) {
|
|
205
|
-
assert.deepEqual(daemon().written, [], `daemon poll ${i + 1} rewrote nothing`);
|
|
206
|
-
assert.deepEqual(cli().written, [], `--out run ${i + 1} rewrote nothing`);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const state = readFoundationState(root);
|
|
210
|
-
assert.equal(typeof state.rendered, "object");
|
|
211
|
-
assert.equal(Object.keys(state.rendered).length, 2, "one stamp per output directory");
|
|
212
|
-
assert.equal(state.renderedAt, state.rendered[resolve(root)], "the top-level stamp still describes the agent dir");
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("a legacy state file with a bare renderedAt migrates without a spurious rewrite", (t) => {
|
|
216
|
-
const root = tmpRoot(t);
|
|
217
|
-
syncDesign({ agentRoot: root, result: result(), now: NOW, name: "Northwind" });
|
|
218
|
-
// Rewrite the snapshot in the pre-map shape, as an older SDK left it.
|
|
219
|
-
const doc = readFoundationState(root);
|
|
220
|
-
const legacy = { ...doc };
|
|
221
|
-
delete legacy.rendered;
|
|
222
|
-
writeFoundationState(root, legacy);
|
|
223
|
-
assert.equal(readFoundationState(root).rendered, undefined);
|
|
224
|
-
|
|
225
|
-
const out = syncDesign({ agentRoot: root, result: result(), now: NOW + 3_600_000, name: "Northwind" });
|
|
226
|
-
assert.deepEqual(out.written, [], "the old stamp is read as the agent dir's, so the bytes still match");
|
|
227
|
-
assert.equal(readFoundationState(root).rendered[resolve(root)], legacy.renderedAt);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("both markdown files are published atomically and forced to 0644 even over a tighter file", (t) => {
|
|
231
|
-
const root = tmpRoot(t);
|
|
232
|
-
syncDesign({ agentRoot: root, result: result(), now: NOW, name: "Northwind" });
|
|
233
|
-
// Some other tool tightens the mode and truncates the content.
|
|
234
|
-
chmodSync(join(root, "DESIGN.md"), 0o600);
|
|
235
|
-
writeFileSync(join(root, "DESIGN.md"), "half a file");
|
|
236
|
-
syncDesign({ agentRoot: root, result: result(), now: NOW + 1000, name: "Northwind" });
|
|
237
|
-
assert.equal(statSync(join(root, "DESIGN.md")).mode & 0o777, DESIGN_FILE_MODE, "chmod runs on a rewrite, not only on create");
|
|
238
|
-
|
|
239
|
-
// No temp file is left behind by the atomic publish.
|
|
240
|
-
assert.deepEqual(readdirSync(root).filter((f) => f.includes(".tmp.")), []);
|
|
241
|
-
});
|
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* alerts.test.mjs — coverage for lib/diagnostics/alerts.mjs.
|
|
3
|
-
*
|
|
4
|
-
* Each behavioral rule must FIRE at/above its threshold and stay QUIET below it,
|
|
5
|
-
* emitAlerts must post every alert to an injected sink (no live call), and both
|
|
6
|
-
* entry points must never throw on garbage input.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { test } from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
evaluateAlerts,
|
|
14
|
-
emitAlerts,
|
|
15
|
-
loadThresholds,
|
|
16
|
-
mergeThresholds,
|
|
17
|
-
DEFAULT_THRESHOLDS,
|
|
18
|
-
_internals,
|
|
19
|
-
} from "./alerts.mjs";
|
|
20
|
-
|
|
21
|
-
const ids = (alerts) => alerts.map((a) => a.id).sort();
|
|
22
|
-
const byId = (alerts, id) => alerts.find((a) => a.id === id);
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// refusal spike
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
test("refusal_spike: quiet below warn, warns at warn, critical at crit", () => {
|
|
29
|
-
// Defaults: warn 10, crit 25, across counters refusal/policy.refusal/classifier.refusal.
|
|
30
|
-
assert.equal(byId(evaluateAlerts({ snapshot: { refusal: 9 } }), "refusal_spike"), undefined);
|
|
31
|
-
|
|
32
|
-
const warn = byId(evaluateAlerts({ snapshot: { refusal: 10 } }), "refusal_spike");
|
|
33
|
-
assert.ok(warn, "fires at warn threshold");
|
|
34
|
-
assert.equal(warn.severity, "warning");
|
|
35
|
-
|
|
36
|
-
// Summed across the configured counters.
|
|
37
|
-
const summed = byId(
|
|
38
|
-
evaluateAlerts({ snapshot: { refusal: 12, "policy.refusal": 8, "classifier.refusal": 6 } }),
|
|
39
|
-
"refusal_spike"
|
|
40
|
-
);
|
|
41
|
-
assert.ok(summed);
|
|
42
|
-
assert.equal(summed.severity, "critical"); // 26 >= 25
|
|
43
|
-
assert.match(summed.detail, /26 refusals/);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// guard fail-open spike (named + suffix match)
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
test("guard_fail_spike: suffix-matched counters are summed and banded", () => {
|
|
51
|
-
// Below warn (5).
|
|
52
|
-
assert.equal(
|
|
53
|
-
byId(evaluateAlerts({ snapshot: { "rate_guard.fail_open": 4 } }), "guard_fail_spike"),
|
|
54
|
-
undefined
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
// At warn: 5 across suffix matches.
|
|
58
|
-
const warn = byId(
|
|
59
|
-
evaluateAlerts({ snapshot: { "rate_guard.fail_open": 3, "budget_guard.persist_failed": 2 } }),
|
|
60
|
-
"guard_fail_spike"
|
|
61
|
-
);
|
|
62
|
-
assert.ok(warn);
|
|
63
|
-
assert.equal(warn.severity, "warning");
|
|
64
|
-
|
|
65
|
-
// At crit: 20.
|
|
66
|
-
const crit = byId(
|
|
67
|
-
evaluateAlerts({ snapshot: { "x.fail_open": 10, "y.degraded": 10 } }),
|
|
68
|
-
"guard_fail_spike"
|
|
69
|
-
);
|
|
70
|
-
assert.ok(crit);
|
|
71
|
-
assert.equal(crit.severity, "critical");
|
|
72
|
-
|
|
73
|
-
// A non-matching counter must NOT contribute.
|
|
74
|
-
assert.equal(
|
|
75
|
-
byId(evaluateAlerts({ snapshot: { "some.unrelated_counter": 99 } }), "guard_fail_spike"),
|
|
76
|
-
undefined
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
// cost per session p99
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
|
|
84
|
-
test("cost_per_session_p99: uses p99 of the distribution when present", () => {
|
|
85
|
-
// 10 cheap sessions, one $4 outlier — p99 (nearest-rank) picks the outlier.
|
|
86
|
-
const perSessionUSD = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 4.0];
|
|
87
|
-
const a = byId(
|
|
88
|
-
evaluateAlerts({ ledger: { sessions: 10, spentUSD: 4.9, perSessionUSD } }),
|
|
89
|
-
"cost_per_session_p99"
|
|
90
|
-
);
|
|
91
|
-
assert.ok(a, "fires when p99 exceeds crit");
|
|
92
|
-
assert.equal(a.severity, "critical"); // 4.0 >= crit 3.0
|
|
93
|
-
assert.match(a.detail, /p99/);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("cost_per_session_p99: falls back to mean when no distribution", () => {
|
|
97
|
-
// mean = 10/5 = 2.0 → warning (>=1.5, <3.0).
|
|
98
|
-
const a = byId(
|
|
99
|
-
evaluateAlerts({ ledger: { sessions: 5, spentUSD: 10 } }),
|
|
100
|
-
"cost_per_session_p99"
|
|
101
|
-
);
|
|
102
|
-
assert.ok(a);
|
|
103
|
-
assert.equal(a.severity, "warning");
|
|
104
|
-
assert.match(a.detail, /mean/);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("cost_per_session_p99: stays quiet below the warn threshold", () => {
|
|
108
|
-
// mean = 5/5 = 1.0 < warn 1.5.
|
|
109
|
-
assert.equal(
|
|
110
|
-
byId(evaluateAlerts({ ledger: { sessions: 5, spentUSD: 5 } }), "cost_per_session_p99"),
|
|
111
|
-
undefined
|
|
112
|
-
);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("cost_per_session_p99: does not fire on a tiny sample (minSessions)", () => {
|
|
116
|
-
// Only 4 sessions (< minSessions 5) even though per-session spend is huge.
|
|
117
|
-
assert.equal(
|
|
118
|
-
byId(evaluateAlerts({ ledger: { sessions: 4, spentUSD: 40 } }), "cost_per_session_p99"),
|
|
119
|
-
undefined
|
|
120
|
-
);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
// absent agent
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
|
|
127
|
-
test("absent_agent: quiet below warn, warns then crits on heartbeat age", () => {
|
|
128
|
-
// Defaults: warn 600s, crit 1800s.
|
|
129
|
-
assert.equal(
|
|
130
|
-
byId(evaluateAlerts({ presence: { heartbeatAgeSec: 300 } }), "absent_agent"),
|
|
131
|
-
undefined
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const warn = byId(evaluateAlerts({ presence: { heartbeatAgeSec: 600 } }), "absent_agent");
|
|
135
|
-
assert.ok(warn);
|
|
136
|
-
assert.equal(warn.severity, "warning");
|
|
137
|
-
|
|
138
|
-
const crit = byId(evaluateAlerts({ presence: { heartbeatAgeSec: 2000 } }), "absent_agent");
|
|
139
|
-
assert.ok(crit);
|
|
140
|
-
assert.equal(crit.severity, "critical");
|
|
141
|
-
|
|
142
|
-
// No heartbeat age supplied → absent_agent does NOT fire (daemon_down covers it).
|
|
143
|
-
assert.equal(byId(evaluateAlerts({ presence: {} }), "absent_agent"), undefined);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// ---------------------------------------------------------------------------
|
|
147
|
-
// daemon down
|
|
148
|
-
// ---------------------------------------------------------------------------
|
|
149
|
-
|
|
150
|
-
test("daemon_down: explicit flag is critical; stale heartbeat is banded", () => {
|
|
151
|
-
const flagged = byId(evaluateAlerts({ presence: { daemonDown: true } }), "daemon_down");
|
|
152
|
-
assert.ok(flagged);
|
|
153
|
-
assert.equal(flagged.severity, "critical");
|
|
154
|
-
|
|
155
|
-
// Defaults: warn 120s, crit 300s.
|
|
156
|
-
assert.equal(
|
|
157
|
-
byId(evaluateAlerts({ presence: { daemonHealthAgeSec: 60 } }), "daemon_down"),
|
|
158
|
-
undefined
|
|
159
|
-
);
|
|
160
|
-
const warn = byId(evaluateAlerts({ presence: { daemonHealthAgeSec: 150 } }), "daemon_down");
|
|
161
|
-
assert.ok(warn);
|
|
162
|
-
assert.equal(warn.severity, "warning");
|
|
163
|
-
const crit = byId(evaluateAlerts({ presence: { daemonHealthAgeSec: 600 } }), "daemon_down");
|
|
164
|
-
assert.ok(crit);
|
|
165
|
-
assert.equal(crit.severity, "critical");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// ---------------------------------------------------------------------------
|
|
169
|
-
// determinism, ordering, custom thresholds
|
|
170
|
-
// ---------------------------------------------------------------------------
|
|
171
|
-
|
|
172
|
-
test("evaluateAlerts: deterministic, critical-first ordering", () => {
|
|
173
|
-
const args = {
|
|
174
|
-
snapshot: { refusal: 30 }, // critical refusal_spike
|
|
175
|
-
presence: { heartbeatAgeSec: 700 }, // warning absent_agent
|
|
176
|
-
};
|
|
177
|
-
const a = evaluateAlerts(args);
|
|
178
|
-
assert.deepEqual(ids(a), ["absent_agent", "refusal_spike"]);
|
|
179
|
-
// Critical sorts before warning.
|
|
180
|
-
assert.equal(a[0].severity, "critical");
|
|
181
|
-
assert.equal(a[0].id, "refusal_spike");
|
|
182
|
-
// Repeated evaluation is identical.
|
|
183
|
-
assert.deepEqual(evaluateAlerts(args), a);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("evaluateAlerts: custom thresholds override defaults", () => {
|
|
187
|
-
// Lower the refusal warn to 2 so 3 refusals fires.
|
|
188
|
-
const a = byId(
|
|
189
|
-
evaluateAlerts({ snapshot: { refusal: 3 }, thresholds: { refusalSpike: { warnCount: 2, critCount: 100 } } }),
|
|
190
|
-
"refusal_spike"
|
|
191
|
-
);
|
|
192
|
-
assert.ok(a);
|
|
193
|
-
assert.equal(a.severity, "warning");
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test("evaluateAlerts: clean inputs produce no alerts", () => {
|
|
197
|
-
const a = evaluateAlerts({
|
|
198
|
-
snapshot: { some_ok_counter: 3 },
|
|
199
|
-
ledger: { sessions: 100, spentUSD: 10 }, // mean 0.1 — fine
|
|
200
|
-
presence: { heartbeatAgeSec: 5, daemonHealthAgeSec: 5 },
|
|
201
|
-
});
|
|
202
|
-
assert.deepEqual(a, []);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("evaluateAlerts: never throws on garbage input", () => {
|
|
206
|
-
assert.doesNotThrow(() => evaluateAlerts());
|
|
207
|
-
assert.doesNotThrow(() => evaluateAlerts(null));
|
|
208
|
-
assert.doesNotThrow(() => evaluateAlerts({ snapshot: 42, ledger: "x", presence: [] }));
|
|
209
|
-
assert.deepEqual(evaluateAlerts({ snapshot: 42, ledger: "x", presence: [] }), []);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
// ---------------------------------------------------------------------------
|
|
213
|
-
// emitAlerts — injected sink, never live
|
|
214
|
-
// ---------------------------------------------------------------------------
|
|
215
|
-
|
|
216
|
-
test("emitAlerts: posts every alert to the injected sink", async () => {
|
|
217
|
-
const posted = [];
|
|
218
|
-
const sink = (alert) => {
|
|
219
|
-
posted.push(alert);
|
|
220
|
-
return true;
|
|
221
|
-
};
|
|
222
|
-
const alerts = [
|
|
223
|
-
{ id: "a", severity: "warning", detail: "x" },
|
|
224
|
-
{ id: "b", severity: "critical", detail: "y" },
|
|
225
|
-
];
|
|
226
|
-
const summary = await emitAlerts(alerts, { sink });
|
|
227
|
-
assert.equal(summary.attempted, 2);
|
|
228
|
-
assert.equal(summary.delivered, 2);
|
|
229
|
-
assert.equal(summary.failed, 0);
|
|
230
|
-
assert.deepEqual(posted.map((a) => a.id), ["a", "b"]);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
test("emitAlerts: async sink is awaited; undefined return counts as delivered", async () => {
|
|
234
|
-
const seen = [];
|
|
235
|
-
const sink = async (alert) => {
|
|
236
|
-
await Promise.resolve();
|
|
237
|
-
seen.push(alert.id);
|
|
238
|
-
// returns undefined
|
|
239
|
-
};
|
|
240
|
-
const summary = await emitAlerts([{ id: "z", severity: "warning", detail: "d" }], { sink });
|
|
241
|
-
assert.equal(summary.delivered, 1);
|
|
242
|
-
assert.deepEqual(seen, ["z"]);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test("emitAlerts: a throwing sink fails that alert but not the rest", async () => {
|
|
246
|
-
const delivered = [];
|
|
247
|
-
const sink = (alert) => {
|
|
248
|
-
if (alert.id === "boom") throw new Error("sink down");
|
|
249
|
-
delivered.push(alert.id);
|
|
250
|
-
return true;
|
|
251
|
-
};
|
|
252
|
-
const summary = await emitAlerts(
|
|
253
|
-
[
|
|
254
|
-
{ id: "ok1", severity: "warning", detail: "" },
|
|
255
|
-
{ id: "boom", severity: "critical", detail: "" },
|
|
256
|
-
{ id: "ok2", severity: "warning", detail: "" },
|
|
257
|
-
],
|
|
258
|
-
{ sink }
|
|
259
|
-
);
|
|
260
|
-
assert.equal(summary.attempted, 3);
|
|
261
|
-
assert.equal(summary.delivered, 2);
|
|
262
|
-
assert.equal(summary.failed, 1);
|
|
263
|
-
assert.deepEqual(delivered, ["ok1", "ok2"]);
|
|
264
|
-
assert.deepEqual(summary.results.find((r) => r.id === "boom"), { id: "boom", ok: false });
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
test("emitAlerts: a sink returning false is a failed delivery", async () => {
|
|
268
|
-
const summary = await emitAlerts([{ id: "a", severity: "warning", detail: "" }], {
|
|
269
|
-
sink: () => false,
|
|
270
|
-
});
|
|
271
|
-
assert.equal(summary.delivered, 0);
|
|
272
|
-
assert.equal(summary.failed, 1);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
test("emitAlerts: no sink wired counts as failed (raised but went nowhere)", async () => {
|
|
276
|
-
const summary = await emitAlerts([{ id: "a", severity: "warning", detail: "" }], {});
|
|
277
|
-
assert.equal(summary.attempted, 1);
|
|
278
|
-
assert.equal(summary.delivered, 0);
|
|
279
|
-
assert.equal(summary.failed, 1);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
test("emitAlerts: never throws on garbage", async () => {
|
|
283
|
-
await assert.doesNotReject(() => emitAlerts());
|
|
284
|
-
await assert.doesNotReject(() => emitAlerts(null, null));
|
|
285
|
-
const s = await emitAlerts("not-an-array", { sink: () => true });
|
|
286
|
-
assert.equal(s.attempted, 0);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
// ---------------------------------------------------------------------------
|
|
290
|
-
// thresholds config
|
|
291
|
-
// ---------------------------------------------------------------------------
|
|
292
|
-
|
|
293
|
-
test("loadThresholds: returns defaults when no config file present", async () => {
|
|
294
|
-
const t = await loadThresholds({ configPath: "/nonexistent/alerts.yaml" });
|
|
295
|
-
assert.equal(t.refusalSpike.warnCount, DEFAULT_THRESHOLDS.refusalSpike.warnCount);
|
|
296
|
-
assert.equal(t.daemonDown.critSec, DEFAULT_THRESHOLDS.daemonDown.critSec);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
test("mergeThresholds: deep-merges override over defaults without mutating defaults", () => {
|
|
300
|
-
const merged = mergeThresholds(DEFAULT_THRESHOLDS, { refusalSpike: { warnCount: 1 } });
|
|
301
|
-
assert.equal(merged.refusalSpike.warnCount, 1);
|
|
302
|
-
// Sibling keys preserved.
|
|
303
|
-
assert.equal(merged.refusalSpike.critCount, DEFAULT_THRESHOLDS.refusalSpike.critCount);
|
|
304
|
-
// Defaults untouched (frozen + cloned).
|
|
305
|
-
assert.equal(DEFAULT_THRESHOLDS.refusalSpike.warnCount, 10);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// ---------------------------------------------------------------------------
|
|
309
|
-
// internals
|
|
310
|
-
// ---------------------------------------------------------------------------
|
|
311
|
-
|
|
312
|
-
test("p99: nearest-rank, tolerant of junk, null on empty", () => {
|
|
313
|
-
assert.equal(_internals.p99([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), 10);
|
|
314
|
-
assert.equal(_internals.p99([5]), 5);
|
|
315
|
-
assert.equal(_internals.p99([]), null);
|
|
316
|
-
assert.equal(_internals.p99("nope"), null);
|
|
317
|
-
assert.equal(_internals.p99([1, "x", 3, null, 2]), 3); // junk dropped → [1,2,3]
|
|
318
|
-
});
|