@cohortapp/agent-sdk 2.17.0 → 2.18.5
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 +18 -5
- 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/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-slots.mjs +234 -0
- package/lib/collective/capture.mjs +8 -6
- package/lib/collective/config.mjs +2 -0
- package/lib/collective/global-config.mjs +63 -1
- package/lib/collective/loop-guard.mjs +155 -0
- package/lib/collective/presence.mjs +142 -5
- package/lib/comms/send-gate.mjs +559 -1
- 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/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +89 -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 +78 -2
- 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 +833 -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 +229 -11
- package/lib/upgrade/ignored-drift.mjs +105 -0
- package/lib/voice/post-call-brief.mjs +30 -17
- package/package.json +13 -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/scaffold/config/collective.yaml +7 -0
- package/scripts/ci/check-durable-write-seam.mjs +3 -1
- 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 +142 -19
- 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 +51 -37
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +595 -149
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +24 -0
- package/scripts/daemon/responder.mjs +246 -79
- 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 +1151 -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 +465 -81
- 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/context/budget.test.mjs +0 -252
- package/lib/context/history-scope.test.mjs +0 -79
- 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 -908
- 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 -406
- 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/dispatcher-session-continuity.test.mjs +0 -365
- 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 -554
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -556
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -221
- 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,486 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* room-budget.test.mjs — one room, one interim, whatever else is going on.
|
|
3
|
-
*
|
|
4
|
-
* The production failure this pins: the interim budget was per OBLIGATION and
|
|
5
|
-
* never per ROOM. Eight agents with N concurrent obligations each in
|
|
6
|
-
* `capital-formation` emitted up to N acks + 2N progress pings + N failure
|
|
7
|
-
* notices into one channel — 9,187 messages in 21 days, ~45 % of them exact
|
|
8
|
-
* duplicates, two independent timers narrating the same overlapping work at the
|
|
9
|
-
* same timestamp. Every one of those decisions was individually correct.
|
|
10
|
-
*
|
|
11
|
-
* Run: node --test lib/assurance/room-budget.test.mjs
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { test, describe, beforeEach } from "node:test";
|
|
15
|
-
import assert from "node:assert/strict";
|
|
16
|
-
import { mkdtempSync, rmSync, readFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
17
|
-
import { tmpdir } from "node:os";
|
|
18
|
-
import { join } from "node:path";
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
DEFAULT_ROOM_INTERIM_WINDOW_MS,
|
|
22
|
-
DEFAULT_ROOM_NOTICE_WINDOW_MS,
|
|
23
|
-
roomInterimWindowMs,
|
|
24
|
-
roomNoticeWindowMs,
|
|
25
|
-
roomKey,
|
|
26
|
-
noticeKey,
|
|
27
|
-
textDigest,
|
|
28
|
-
interimAllowed,
|
|
29
|
-
noticeAllowed,
|
|
30
|
-
recordInterim,
|
|
31
|
-
recordNotice,
|
|
32
|
-
pruneLedger,
|
|
33
|
-
claimRoomInterim,
|
|
34
|
-
claimRoomNotice,
|
|
35
|
-
ledgerPath,
|
|
36
|
-
LEDGER_BASENAME,
|
|
37
|
-
readLedger,
|
|
38
|
-
_emptyLedger,
|
|
39
|
-
} from "./room-budget.mjs";
|
|
40
|
-
|
|
41
|
-
const ROOM_INTERIM_WINDOW_MS = DEFAULT_ROOM_INTERIM_WINDOW_MS;
|
|
42
|
-
|
|
43
|
-
const T0 = 1_757_000_000_000; // a fixed instant; nothing here reads a clock
|
|
44
|
-
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
// The pure half
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
|
|
49
|
-
describe("roomKey — a room is (service, channel) and nothing else", () => {
|
|
50
|
-
test("service and channel together name the room", () => {
|
|
51
|
-
assert.equal(roomKey({ service: "cohort", channel: "C-CAPFORM" }), "cohort|c-capform");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("case and surrounding space never make two rooms out of one", () => {
|
|
55
|
-
assert.equal(roomKey({ service: "Cohort", channel: " C-CapForm " }), roomKey({ service: "cohort", channel: "c-capform" }));
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("a missing channel is still a room, not a crash", () => {
|
|
59
|
-
assert.equal(typeof roomKey({ service: "cohort", channel: null }), "string");
|
|
60
|
-
assert.equal(typeof roomKey({}), "string");
|
|
61
|
-
assert.equal(typeof roomKey(), "string");
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test("two different channels on one service are two rooms", () => {
|
|
65
|
-
assert.notEqual(roomKey({ service: "cohort", channel: "A" }), roomKey({ service: "cohort", channel: "B" }));
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("the same channel name on two services is two rooms", () => {
|
|
69
|
-
assert.notEqual(roomKey({ service: "cohort", channel: "general" }), roomKey({ service: "slack", channel: "general" }));
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe("interimAllowed — the pure decision", () => {
|
|
74
|
-
test("an empty ledger allows the first interim", () => {
|
|
75
|
-
const v = interimAllowed({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
|
|
76
|
-
assert.equal(v.allowed, true);
|
|
77
|
-
assert.equal(v.reason, "room-budget-free");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("a second interim inside the window is refused, and says which room and when", () => {
|
|
81
|
-
const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
|
|
82
|
-
const v = interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + 60_000 });
|
|
83
|
-
assert.equal(v.allowed, false);
|
|
84
|
-
assert.equal(v.reason, "room-budget-spent");
|
|
85
|
-
assert.equal(v.lastAt, T0);
|
|
86
|
-
assert.equal(v.room, roomKey({ service: "cohort", channel: "C1" }));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("the window reopens exactly at the boundary, not a millisecond early", () => {
|
|
90
|
-
const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
|
|
91
|
-
assert.equal(interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + ROOM_INTERIM_WINDOW_MS - 1 }).allowed, false);
|
|
92
|
-
assert.equal(interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + ROOM_INTERIM_WINDOW_MS }).allowed, true);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("a spent room never silences a DIFFERENT room", () => {
|
|
96
|
-
const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
|
|
97
|
-
assert.equal(interimAllowed({ ledger: led, service: "cohort", channel: "C2", now: T0 + 1 }).allowed, true);
|
|
98
|
-
assert.equal(interimAllowed({ ledger: led, service: "slack", channel: "C1", now: T0 + 1 }).allowed, true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("a clock that goes backwards does not hand out a free interim", () => {
|
|
102
|
-
// NTP steps, a restored snapshot, a test harness. `now` before the recorded
|
|
103
|
-
// stamp must read as "inside the window", never as "the window elapsed".
|
|
104
|
-
const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
|
|
105
|
-
assert.equal(interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 - 60 * 60_000 }).allowed, false);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("a corrupt or absent entry reads as 'never spoken here', never as a permanent gag", () => {
|
|
109
|
-
for (const bad of [{ rooms: null }, { rooms: { "cohort|c1": "yesterday" } }, {}, null, undefined, "nonsense"]) {
|
|
110
|
-
assert.equal(interimAllowed({ ledger: bad, service: "cohort", channel: "C1", now: T0 }).allowed, true, `${JSON.stringify(bad)} must fail open`);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("the decision is pure: it never mutates the ledger it is given", () => {
|
|
115
|
-
const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
|
|
116
|
-
const before = JSON.stringify(led);
|
|
117
|
-
interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + 1 });
|
|
118
|
-
assert.equal(JSON.stringify(led), before);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("the window is env-tunable and defaults to fifteen minutes", () => {
|
|
122
|
-
assert.equal(DEFAULT_ROOM_INTERIM_WINDOW_MS, 15 * 60_000);
|
|
123
|
-
const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
|
|
124
|
-
assert.equal(interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + 2000, windowMs: 1000 }).allowed, true);
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe("recordInterim / pruneLedger", () => {
|
|
129
|
-
test("recording returns a NEW ledger and leaves the old one untouched", () => {
|
|
130
|
-
const led = _emptyLedger();
|
|
131
|
-
const next = recordInterim({ ledger: led, service: "cohort", channel: "C1", now: T0 });
|
|
132
|
-
assert.notEqual(led, next);
|
|
133
|
-
assert.deepEqual(led.rooms, {});
|
|
134
|
-
assert.equal(next.rooms[roomKey({ service: "cohort", channel: "C1" })], T0);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("the ledger does not grow without bound — entries older than two windows are dropped", () => {
|
|
138
|
-
// The store is a durable file on a machine that runs for months. An entry
|
|
139
|
-
// that can never refuse anything again is landfill.
|
|
140
|
-
let led = _emptyLedger();
|
|
141
|
-
for (let i = 0; i < 50; i++) led = recordInterim({ ledger: led, service: "cohort", channel: `OLD-${i}`, now: T0 });
|
|
142
|
-
led = recordInterim({ ledger: led, service: "cohort", channel: "FRESH", now: T0 + 3 * ROOM_INTERIM_WINDOW_MS });
|
|
143
|
-
const pruned = pruneLedger({ ledger: led, now: T0 + 3 * ROOM_INTERIM_WINDOW_MS });
|
|
144
|
-
assert.equal(Object.keys(pruned.rooms).length, 1);
|
|
145
|
-
assert.ok(pruned.rooms[roomKey({ service: "cohort", channel: "FRESH" })]);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("pruning never drops an entry that can still refuse an interim", () => {
|
|
149
|
-
const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
|
|
150
|
-
const pruned = pruneLedger({ ledger: led, now: T0 + ROOM_INTERIM_WINDOW_MS });
|
|
151
|
-
assert.equal(interimAllowed({ ledger: pruned, service: "cohort", channel: "C1", now: T0 + ROOM_INTERIM_WINDOW_MS - 1 }).allowed, false);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// ---------------------------------------------------------------------------
|
|
156
|
-
// The durable edge
|
|
157
|
-
// ---------------------------------------------------------------------------
|
|
158
|
-
|
|
159
|
-
describe("claimRoomInterim — the durable edge, with an injected clock and fs", () => {
|
|
160
|
-
let ROOT;
|
|
161
|
-
beforeEach(() => {
|
|
162
|
-
if (ROOT) { try { rmSync(ROOT, { recursive: true, force: true }); } catch { /* */ } }
|
|
163
|
-
ROOT = mkdtempSync(join(tmpdir(), "room-budget-"));
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("THE FLOOD FIX: N concurrent obligations in one room yield exactly ONE interim", () => {
|
|
167
|
-
// 8 agents' worth of concurrent asks, arriving together, as measured.
|
|
168
|
-
const said = [];
|
|
169
|
-
for (let i = 0; i < 40; i++) {
|
|
170
|
-
const v = claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + i * 1000, agentRoot: ROOT });
|
|
171
|
-
if (v.allowed) said.push(i);
|
|
172
|
-
}
|
|
173
|
-
assert.deepEqual(said, [0], `expected one interim across 40 obligations, got ${said.length}`);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test("…and the next window allows exactly one more, not a backlog burst", () => {
|
|
177
|
-
for (let i = 0; i < 40; i++) claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + i * 1000, agentRoot: ROOT });
|
|
178
|
-
let allowed = 0;
|
|
179
|
-
for (let i = 0; i < 40; i++) {
|
|
180
|
-
if (claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + ROOM_INTERIM_WINDOW_MS + i * 1000, agentRoot: ROOT }).allowed) allowed++;
|
|
181
|
-
}
|
|
182
|
-
assert.equal(allowed, 1);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("a refused claim writes nothing and does not move the window", () => {
|
|
186
|
-
claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT });
|
|
187
|
-
claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 10 * 60_000, agentRoot: ROOT });
|
|
188
|
-
// If the refused claim had stamped the ledger, the window would run from
|
|
189
|
-
// T0+10min and the room would stay gagged past T0+15min.
|
|
190
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + ROOM_INTERIM_WINDOW_MS, agentRoot: ROOT }).allowed, true);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test("busy rooms never gag quiet ones", () => {
|
|
194
|
-
for (let i = 0; i < 20; i++) claimRoomInterim({ service: "cohort", channel: "busy", now: T0 + i, agentRoot: ROOT });
|
|
195
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "quiet", now: T0 + 100, agentRoot: ROOT }).allowed, true);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("the store is durable — a fresh process sees the spend", () => {
|
|
199
|
-
claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT });
|
|
200
|
-
const onDisk = JSON.parse(readFileSync(ledgerPath(ROOT), "utf-8"));
|
|
201
|
-
assert.equal(onDisk.rooms[roomKey({ service: "cohort", channel: "C1" })], T0);
|
|
202
|
-
assert.equal(readLedger({ agentRoot: ROOT }).rooms[roomKey({ service: "cohort", channel: "C1" })], T0);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("it lives beside the obligation ledger it governs", () => {
|
|
206
|
-
assert.equal(ledgerPath(ROOT), join(ROOT, "state", "obligations", "room-budget.json"));
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
test("an unreadable store fails OPEN — a broken file may not silence a human", () => {
|
|
210
|
-
mkdirSync(join(ROOT, "state", "obligations"), { recursive: true });
|
|
211
|
-
writeFileSync(ledgerPath(ROOT), "{ this is not json");
|
|
212
|
-
const v = claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT });
|
|
213
|
-
assert.equal(v.allowed, true);
|
|
214
|
-
assert.equal(v.degraded, true, "…but it SAYS it is degraded, so the condition is measurable");
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test("an unwritable store still lets the message out, and says so", () => {
|
|
218
|
-
const v = claimRoomInterim({
|
|
219
|
-
service: "cohort", channel: "C1", now: T0, agentRoot: ROOT,
|
|
220
|
-
deps: { writeImpl: () => { throw new Error("EROFS"); } },
|
|
221
|
-
});
|
|
222
|
-
assert.equal(v.allowed, true);
|
|
223
|
-
assert.equal(v.degraded, true);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
test("the clock is injected, never read from the wall", () => {
|
|
227
|
-
// Passing `now` is the contract; omitting it is a caller bug, not a licence
|
|
228
|
-
// to read Date.now() from inside a decision module.
|
|
229
|
-
const v = claimRoomInterim({ service: "cohort", channel: "C1", agentRoot: ROOT });
|
|
230
|
-
assert.equal(v.allowed, false);
|
|
231
|
-
assert.equal(v.reason, "no-clock");
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test("fs is injectable end to end — no real filesystem needed", () => {
|
|
235
|
-
let stored = null;
|
|
236
|
-
const deps = {
|
|
237
|
-
readImpl: () => stored,
|
|
238
|
-
writeImpl: (_p, obj) => { stored = obj; },
|
|
239
|
-
};
|
|
240
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT, deps }).allowed, true);
|
|
241
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 1, agentRoot: ROOT, deps }).allowed, false);
|
|
242
|
-
assert.equal(stored.rooms[roomKey({ service: "cohort", channel: "C1" })], T0);
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
describe("claimRoomInterim commit:false — ask without spending", () => {
|
|
247
|
-
let ROOT;
|
|
248
|
-
beforeEach(() => {
|
|
249
|
-
if (ROOT) { try { rmSync(ROOT, { recursive: true, force: true }); } catch { /* */ } }
|
|
250
|
-
ROOT = mkdtempSync(join(tmpdir(), "room-budget-peek-"));
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
test("a peek does not spend the budget", () => {
|
|
254
|
-
// The interim's text comes from a model call that can fail. Spending the
|
|
255
|
-
// room's fifteen minutes BEFORE the message exists gags the room in
|
|
256
|
-
// exchange for nothing — so the caller asks first, composes, then claims.
|
|
257
|
-
const peek = claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT, commit: false });
|
|
258
|
-
assert.equal(peek.allowed, true);
|
|
259
|
-
assert.equal(peek.committed, false);
|
|
260
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 1, agentRoot: ROOT }).allowed, true, "the peek left the budget unspent");
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
test("a peek still reports a spent room", () => {
|
|
264
|
-
claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT });
|
|
265
|
-
const peek = claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 1000, agentRoot: ROOT, commit: false });
|
|
266
|
-
assert.equal(peek.allowed, false);
|
|
267
|
-
assert.equal(peek.reason, "room-budget-spent");
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
test("a committing claim says so", () => {
|
|
271
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT }).committed, true);
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// ---------------------------------------------------------------------------
|
|
276
|
-
// The HONEST SCOPE of the guarantee
|
|
277
|
-
// ---------------------------------------------------------------------------
|
|
278
|
-
|
|
279
|
-
describe("the budget is per (SEAT, service, channel) — not per (service, channel)", () => {
|
|
280
|
-
let A, B;
|
|
281
|
-
beforeEach(() => {
|
|
282
|
-
A = mkdtempSync(join(tmpdir(), "room-seat-a-"));
|
|
283
|
-
B = mkdtempSync(join(tmpdir(), "room-seat-b-"));
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test("two seats in ONE room each get their own interim, because they share no store", () => {
|
|
287
|
-
// This is the arithmetic the module header now states rather than glosses.
|
|
288
|
-
// `agentRoot` is AGENT_DIR — one agent's repo on one Mac mini. There is no
|
|
289
|
-
// shared filesystem between seats, so the guarantee this module can make is
|
|
290
|
-
// per seat. The measured room had EIGHT seats in it: the ceiling this gate
|
|
291
|
-
// alone imposes there is 8 x (60/15) = 32 interims an hour, against a
|
|
292
|
-
// measured content-free rate of ~14/h. It is the backstop, not the thing
|
|
293
|
-
// that removed the measured flood.
|
|
294
|
-
//
|
|
295
|
-
// Pinned as a TEST rather than left in prose because the comment that said
|
|
296
|
-
// otherwise survived a review: an operator who reads "one room, one message"
|
|
297
|
-
// will size the next change wrong.
|
|
298
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0, agentRoot: A }).allowed, true);
|
|
299
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + 1, agentRoot: B }).allowed, true,
|
|
300
|
-
"a second SEAT is not gagged by the first — there is no store between them");
|
|
301
|
-
// …and within either seat the guarantee still holds absolutely.
|
|
302
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + 2, agentRoot: A }).allowed, false);
|
|
303
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + 3, agentRoot: B }).allowed, false);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
test("the eight-seat ceiling is exactly eight per window, and no more", () => {
|
|
307
|
-
const seats = [];
|
|
308
|
-
for (let i = 0; i < 8; i++) seats.push(mkdtempSync(join(tmpdir(), `room-seat-${i}-`)));
|
|
309
|
-
let spoke = 0;
|
|
310
|
-
// Each seat tries ten times inside one window; each may speak once.
|
|
311
|
-
for (const seat of seats) {
|
|
312
|
-
for (let i = 0; i < 10; i++) {
|
|
313
|
-
if (claimRoomInterim({ service: "cohort", channel: "capital-formation", now: T0 + i, agentRoot: seat }).allowed) spoke++;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
assert.equal(spoke, 8, "eight seats, eight interims per window — the honest ceiling");
|
|
317
|
-
for (const seat of seats) rmSync(seat, { recursive: true, force: true });
|
|
318
|
-
});
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
describe("the window default is resolved at the EDGE, never captured at import", () => {
|
|
322
|
-
// The gate report for this package claimed the pure modules read no
|
|
323
|
-
// clock/env/fs. A `const X = process.env.Y` at module scope falsified that
|
|
324
|
-
// and, more concretely, meant an operator who exported the variable after the
|
|
325
|
-
// first import silently got the stale default.
|
|
326
|
-
test("roomInterimWindowMs reads the env it is given, every time it is called", () => {
|
|
327
|
-
assert.equal(roomInterimWindowMs({}), DEFAULT_ROOM_INTERIM_WINDOW_MS);
|
|
328
|
-
assert.equal(roomInterimWindowMs({ ASSURANCE_ROOM_INTERIM_WINDOW_MS: "60000" }), 60_000);
|
|
329
|
-
assert.equal(roomNoticeWindowMs({ ASSURANCE_ROOM_NOTICE_WINDOW_MS: "1000" }), 1000);
|
|
330
|
-
// Garbage falls back rather than producing NaN, which would compare false
|
|
331
|
-
// against every elapsed time and make the budget decorative.
|
|
332
|
-
assert.equal(roomInterimWindowMs({ ASSURANCE_ROOM_INTERIM_WINDOW_MS: "soon" }), DEFAULT_ROOM_INTERIM_WINDOW_MS);
|
|
333
|
-
assert.equal(roomNoticeWindowMs(undefined) > 0, true);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
test("an env set AFTER this module was first imported still takes effect", () => {
|
|
337
|
-
const ROOT = mkdtempSync(join(tmpdir(), "room-env-"));
|
|
338
|
-
const prev = process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS;
|
|
339
|
-
try {
|
|
340
|
-
process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS = "1000";
|
|
341
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT }).allowed, true);
|
|
342
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 999, agentRoot: ROOT }).allowed, false);
|
|
343
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 1000, agentRoot: ROOT }).allowed, true,
|
|
344
|
-
"the one-second window was honoured, so the value was not folded at import time");
|
|
345
|
-
} finally {
|
|
346
|
-
if (prev === undefined) delete process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS;
|
|
347
|
-
else process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS = prev;
|
|
348
|
-
rmSync(ROOT, { recursive: true, force: true });
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
test("the pure decision never reads the environment — it defaults to the CONSTANT", () => {
|
|
353
|
-
const prev = process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS;
|
|
354
|
-
try {
|
|
355
|
-
process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS = "1000";
|
|
356
|
-
const led = recordInterim({ ledger: _emptyLedger(), service: "cohort", channel: "C1", now: T0 });
|
|
357
|
-
assert.equal(
|
|
358
|
-
interimAllowed({ ledger: led, service: "cohort", channel: "C1", now: T0 + 2000 }).allowed,
|
|
359
|
-
false,
|
|
360
|
-
"the pure function used the 15-minute constant, not the environment",
|
|
361
|
-
);
|
|
362
|
-
} finally {
|
|
363
|
-
if (prev === undefined) delete process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS;
|
|
364
|
-
else process.env.ASSURANCE_ROOM_INTERIM_WINDOW_MS = prev;
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
// ---------------------------------------------------------------------------
|
|
370
|
-
// Identical outcome notices
|
|
371
|
-
// ---------------------------------------------------------------------------
|
|
372
|
-
|
|
373
|
-
describe("textDigest / noticeKey", () => {
|
|
374
|
-
test("the same sentence digests the same however it is spaced", () => {
|
|
375
|
-
assert.equal(textDigest("Couldn't finish this — it timed out."), textDigest(" Couldn't finish this — it timed out. "));
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
test("a different sentence digests differently", () => {
|
|
379
|
-
assert.notEqual(textDigest("Hit a problem — it timed out. Retrying now."), textDigest("Couldn't finish this — it timed out."));
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
test("the key separates room, notice id and sentence", () => {
|
|
383
|
-
const base = { service: "cohort", channel: "C1", notice: "failure:final", text: "same" };
|
|
384
|
-
assert.notEqual(noticeKey(base), noticeKey({ ...base, channel: "C2" }));
|
|
385
|
-
assert.notEqual(noticeKey(base), noticeKey({ ...base, notice: "failure:retrying" }));
|
|
386
|
-
assert.notEqual(noticeKey(base), noticeKey({ ...base, text: "different" }));
|
|
387
|
-
assert.equal(noticeKey(base), noticeKey({ ...base }));
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
describe("claimRoomNotice — a byte-identical outcome notice is not said twice in one room", () => {
|
|
392
|
-
let ROOT;
|
|
393
|
-
const STALE = "Couldn't finish this — the work never came back with a result and has now outrun its time limit. I've stopped retrying and flagged it so it isn't lost.";
|
|
394
|
-
beforeEach(() => { ROOT = mkdtempSync(join(tmpdir(), "room-notice-")); });
|
|
395
|
-
|
|
396
|
-
test("THE SIBLING CASE: twenty debts, one sentence, one message", () => {
|
|
397
|
-
// The per-obligation notice ledger cannot reach this: twenty separate asks
|
|
398
|
-
// are twenty separate records, and the stale sentence carries nothing about
|
|
399
|
-
// which ask it is, so the reader gets the identical line twenty times and
|
|
400
|
-
// cannot tell any of them apart.
|
|
401
|
-
let said = 0;
|
|
402
|
-
for (let i = 0; i < 20; i++) {
|
|
403
|
-
if (claimRoomNotice({ service: "cohort", channel: "C-CAPFORM", notice: "failure:final", text: STALE, now: T0 + i * 1000, agentRoot: ROOT }).allowed) said++;
|
|
404
|
-
}
|
|
405
|
-
assert.equal(said, 1, `twenty identical failure sentences must reach one room once — got ${said}`);
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
test("a DIFFERENT sentence is never suppressed, at any volume", () => {
|
|
409
|
-
// This is the safety argument. Suppressing information a reader does not
|
|
410
|
-
// have re-creates the original bug: a person never told their work died.
|
|
411
|
-
let said = 0;
|
|
412
|
-
for (let i = 0; i < 20; i++) {
|
|
413
|
-
const text = `Couldn't finish this — cause number ${i}. I've stopped retrying.`;
|
|
414
|
-
if (claimRoomNotice({ service: "cohort", channel: "C-CAPFORM", notice: "failure:final", text, now: T0 + i * 1000, agentRoot: ROOT }).allowed) said++;
|
|
415
|
-
}
|
|
416
|
-
assert.equal(said, 20, "every distinct fact is still said");
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
test("'a retry is running' and 'I have stopped' stay separately sayable", () => {
|
|
420
|
-
const retrying = "Hit a problem — it timed out. Retrying now; if it fails again I'll come straight back.";
|
|
421
|
-
assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:retrying", text: retrying, now: T0, agentRoot: ROOT }).allowed, true);
|
|
422
|
-
assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + 1, agentRoot: ROOT }).allowed, true);
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
test("a different ROOM is never gagged by a busy one", () => {
|
|
426
|
-
claimRoomNotice({ service: "cohort", channel: "busy", notice: "failure:final", text: STALE, now: T0, agentRoot: ROOT });
|
|
427
|
-
assert.equal(claimRoomNotice({ service: "cohort", channel: "quiet", notice: "failure:final", text: STALE, now: T0 + 1, agentRoot: ROOT }).allowed, true);
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
test("the window reopens, because the same fact a day later is news again", () => {
|
|
431
|
-
claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0, agentRoot: ROOT });
|
|
432
|
-
assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + DEFAULT_ROOM_NOTICE_WINDOW_MS - 1, agentRoot: ROOT }).allowed, false);
|
|
433
|
-
assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + DEFAULT_ROOM_NOTICE_WINDOW_MS, agentRoot: ROOT }).allowed, true);
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
test("commit:false asks without spending", () => {
|
|
437
|
-
const peek = claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0, agentRoot: ROOT, commit: false });
|
|
438
|
-
assert.equal(peek.allowed, true);
|
|
439
|
-
assert.equal(peek.committed, false);
|
|
440
|
-
assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + 1, agentRoot: ROOT }).allowed, true,
|
|
441
|
-
"the peek left it unspent — a send that never happened must not silence the next one");
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
test("FAIL-OPEN, and the OPPOSITE way to the interim: no clock ⇒ say it", () => {
|
|
445
|
-
// An interim is a courtesy and is safe to drop. "Your work died" is not.
|
|
446
|
-
const v = claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, agentRoot: ROOT });
|
|
447
|
-
assert.equal(v.allowed, true);
|
|
448
|
-
assert.equal(v.degraded, true);
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
test("an unreadable ledger lets the notice through, flagged", () => {
|
|
452
|
-
mkdirSync(join(ROOT, "state", "obligations"), { recursive: true });
|
|
453
|
-
writeFileSync(ledgerPath(ROOT), "{ not json");
|
|
454
|
-
const v = claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0, agentRoot: ROOT });
|
|
455
|
-
assert.equal(v.allowed, true);
|
|
456
|
-
assert.equal(v.degraded, true);
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
test("interims and notices share one file without evicting each other", () => {
|
|
460
|
-
claimRoomInterim({ service: "cohort", channel: "C1", now: T0, agentRoot: ROOT });
|
|
461
|
-
claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + 1, agentRoot: ROOT });
|
|
462
|
-
const led = JSON.parse(readFileSync(ledgerPath(ROOT), "utf-8"));
|
|
463
|
-
assert.equal(Object.keys(led.rooms).length, 1, "the interim spend survived the notice write");
|
|
464
|
-
assert.equal(Object.keys(led.notices).length, 1);
|
|
465
|
-
assert.equal(claimRoomInterim({ service: "cohort", channel: "C1", now: T0 + 2, agentRoot: ROOT }).allowed, false);
|
|
466
|
-
assert.equal(claimRoomNotice({ service: "cohort", channel: "C1", notice: "failure:final", text: STALE, now: T0 + 3, agentRoot: ROOT }).allowed, false);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test("the notice map is pruned too, so the file cannot grow without bound", () => {
|
|
470
|
-
let led = _emptyLedger();
|
|
471
|
-
for (let i = 0; i < 50; i++) {
|
|
472
|
-
led = recordNotice({ ledger: led, service: "cohort", channel: "C1", notice: "failure:final", text: `old ${i}`, now: T0 });
|
|
473
|
-
}
|
|
474
|
-
const pruned = pruneLedger({ ledger: led, now: T0 + 3 * DEFAULT_ROOM_NOTICE_WINDOW_MS });
|
|
475
|
-
assert.equal(Object.keys(pruned.notices).length, 0);
|
|
476
|
-
});
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
describe("the ledger basename is exported, because its reader has to skip it", () => {
|
|
480
|
-
test("LEDGER_BASENAME is the file ledgerPath produces", () => {
|
|
481
|
-
// `assurance.listObligations` parses every *.json in this directory as a
|
|
482
|
-
// debt record. It skips this one BY NAME, so the name must be one string.
|
|
483
|
-
assert.equal(ledgerPath("/tmp/x").endsWith(`/${LEDGER_BASENAME}`), true);
|
|
484
|
-
assert.equal(LEDGER_BASENAME, "room-budget.json");
|
|
485
|
-
});
|
|
486
|
-
});
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tier.test.mjs — the reply tier, across the whole classifier matrix.
|
|
3
|
-
*
|
|
4
|
-
* The tier is the ONE decision that says whether a human hears anything before
|
|
5
|
-
* the answer. Getting it wrong in the loud direction is the 3,069 generic acks
|
|
6
|
-
* and 961 clock-derived nags measured in production on 2026-09-12; getting it
|
|
7
|
-
* wrong in the quiet direction is the silence the obligation ledger was built
|
|
8
|
-
* to end. So every cell of the matrix is pinned here rather than sampled.
|
|
9
|
-
*
|
|
10
|
-
* Run: node --test lib/assurance/tier.test.mjs
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { test, describe } from "node:test";
|
|
14
|
-
import assert from "node:assert/strict";
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
replyTier,
|
|
18
|
-
TIERS,
|
|
19
|
-
ANSWER_MAX_RUNG,
|
|
20
|
-
PLAN_MIN_RUNG,
|
|
21
|
-
PLAN_ACTIONS,
|
|
22
|
-
PLAN_PRIORITIES,
|
|
23
|
-
} from "./tier.mjs";
|
|
24
|
-
|
|
25
|
-
/** Every value `classifier.mjs` can emit — the real enums, not a sample. */
|
|
26
|
-
const ACTIONS = ["respond", "draft", "research", "queue", "archive", "ignore"];
|
|
27
|
-
const PRIORITIES = ["critical", "high", "normal", "ignore"];
|
|
28
|
-
/** Every rung in `lib/execution/route.RUNGS`, plus "not routed yet". */
|
|
29
|
-
const RUNGS = [0, 1, 2, 3, 4, 5, null];
|
|
30
|
-
|
|
31
|
-
describe("replyTier — the three tiers and nothing else", () => {
|
|
32
|
-
test("every cell of the matrix returns one of exactly three tiers", () => {
|
|
33
|
-
for (const action of ACTIONS) {
|
|
34
|
-
for (const priority of PRIORITIES) {
|
|
35
|
-
for (const rung of RUNGS) {
|
|
36
|
-
for (const answerable of [true, false, null, undefined]) {
|
|
37
|
-
for (const willSpawnSession of [true, false]) {
|
|
38
|
-
const t = replyTier({ answerable, action, priority, rung, willSpawnSession });
|
|
39
|
-
assert.ok(TIERS.includes(t), `{${action}/${priority}/rung:${rung}/answerable:${answerable}/spawn:${willSpawnSession}} → ${t}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("no session spawning ⇒ answer tier, whatever the classifier said", () => {
|
|
48
|
-
// The reply arrives in this turn. An interim in front of an answer the
|
|
49
|
-
// human is about to read is the definition of content-free traffic.
|
|
50
|
-
for (const action of ACTIONS) {
|
|
51
|
-
for (const priority of PRIORITIES) {
|
|
52
|
-
for (const rung of RUNGS) {
|
|
53
|
-
assert.equal(
|
|
54
|
-
replyTier({ answerable: false, action, priority, rung, willSpawnSession: false }),
|
|
55
|
-
"answer",
|
|
56
|
-
`${action}/${priority}/rung:${rung} with no session must be "answer"`,
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("answerable at rung 0 or 1 is the answer tier — the reply IS the acknowledgement", () => {
|
|
64
|
-
// `willSpawnSession` UNKNOWN: the caller is asking hypothetically (which is
|
|
65
|
-
// how WP-2's effort router will use it), so the classifier verdict decides.
|
|
66
|
-
for (const rung of [0, ANSWER_MAX_RUNG]) {
|
|
67
|
-
assert.equal(replyTier({ answerable: true, action: "respond", priority: "high", rung }), "answer");
|
|
68
|
-
assert.equal(replyTier({ answerable: true, action: "respond", priority: "high", rung, willSpawnSession: false }), "answer");
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("REGRESSION: an ASSERTED session disqualifies the answer tier, however answerable the item looked", () => {
|
|
73
|
-
// THE QUICK-PATH FALL-THROUGH. agent-daemon.mjs tries a quick reply first;
|
|
74
|
-
// when that reply fails transiently or is blocked by validation it falls
|
|
75
|
-
// THROUGH to a full session dispatch and asks for a tier with
|
|
76
|
-
// willSpawnSession:true while classResult.answerable is still true. The
|
|
77
|
-
// answer tier's whole premise — "the reply arrives in this turn" — is
|
|
78
|
-
// exactly what the fall-through has falsified.
|
|
79
|
-
//
|
|
80
|
-
// Latent rather than live only because `rung` is null today (R13). The
|
|
81
|
-
// moment WP-2 routes it, that item would have landed in `answer`,
|
|
82
|
-
// shouldAcknowledge would have returned ack:false, the sweep would have
|
|
83
|
-
// read the durable tier as never-speak, and a 15-45 minute session would
|
|
84
|
-
// have run with the human hearing NOTHING at all. Pinned before WP-2 lands
|
|
85
|
-
// on top of it.
|
|
86
|
-
for (const rung of [0, ANSWER_MAX_RUNG]) {
|
|
87
|
-
assert.equal(
|
|
88
|
-
replyTier({ answerable: true, action: "respond", priority: "high", rung, willSpawnSession: true }),
|
|
89
|
-
"work",
|
|
90
|
-
`rung ${rung}: a spawning session speaks once at most — it is never silent`,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
// …and the plan tier still outranks it, because rung 3+ is a project.
|
|
94
|
-
assert.equal(replyTier({ answerable: true, action: "respond", priority: "high", rung: 4, willSpawnSession: true }), "plan");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("a routed rung of 3 or above is the plan tier, whatever the action", () => {
|
|
98
|
-
for (const rung of RUNGS.filter((r) => r != null && r >= PLAN_MIN_RUNG)) {
|
|
99
|
-
for (const action of ACTIONS) {
|
|
100
|
-
assert.equal(
|
|
101
|
-
replyTier({ answerable: false, action, priority: "normal", rung, willSpawnSession: true }),
|
|
102
|
-
"plan",
|
|
103
|
-
`rung ${rung} / ${action} must be "plan"`,
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("research or draft at critical/high priority is the plan tier even with no routed rung", () => {
|
|
110
|
-
for (const action of PLAN_ACTIONS) {
|
|
111
|
-
for (const priority of PLAN_PRIORITIES) {
|
|
112
|
-
assert.equal(replyTier({ answerable: false, action, priority, rung: null, willSpawnSession: true }), "plan");
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("…and the same actions at normal priority are only work", () => {
|
|
118
|
-
for (const action of PLAN_ACTIONS) {
|
|
119
|
-
for (const priority of ["normal", "ignore"]) {
|
|
120
|
-
assert.equal(replyTier({ answerable: false, action, priority, rung: null, willSpawnSession: true }), "work");
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("the ordinary directed ask — respond/high, unrouted — is work: silence, then at most one line", () => {
|
|
126
|
-
// This is the shape of the overwhelming majority of the 10,667 measured
|
|
127
|
-
// agent messages. It must NOT be plan tier, or the flood returns wearing a
|
|
128
|
-
// better costume.
|
|
129
|
-
assert.equal(replyTier({ answerable: false, action: "respond", priority: "high", rung: null, willSpawnSession: true }), "work");
|
|
130
|
-
assert.equal(replyTier({ answerable: false, action: "queue", priority: "critical", rung: null, willSpawnSession: true }), "work");
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("an ABSENT rung is never read as the cheapest rung", () => {
|
|
134
|
-
// `lib/backlog` already learned this: absence is not rung 0. A missing rung
|
|
135
|
-
// may not buy the answer tier's silence-on-the-strength-of-a-quick-path,
|
|
136
|
-
// and may not buy the plan tier's licence to speak either.
|
|
137
|
-
assert.equal(replyTier({ answerable: true, action: "respond", priority: "high", rung: null, willSpawnSession: true }), "work");
|
|
138
|
-
assert.equal(replyTier({ answerable: true, action: "respond", priority: "high", rung: undefined, willSpawnSession: true }), "work");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("a non-numeric or out-of-range rung is treated as unrouted, never as a licence to speak", () => {
|
|
142
|
-
for (const rung of ["3", "team", NaN, Infinity, -1, 6, {}, []]) {
|
|
143
|
-
assert.equal(
|
|
144
|
-
replyTier({ answerable: false, action: "respond", priority: "normal", rung, willSpawnSession: true }),
|
|
145
|
-
"work",
|
|
146
|
-
`rung ${JSON.stringify(rung)} must not be trusted`,
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("answerable only counts when it is strictly true", () => {
|
|
152
|
-
// classifier.mjs coerces a missing/odd field to false for the same reason:
|
|
153
|
-
// never guess "answerable".
|
|
154
|
-
for (const answerable of ["true", 1, {}, null, undefined]) {
|
|
155
|
-
assert.equal(replyTier({ answerable, action: "respond", priority: "high", rung: 0, willSpawnSession: true }), "work");
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test("garbage in gives work out — the tier never throws and never invents silence", () => {
|
|
160
|
-
// Fail-open direction: the tier that can still speak, never the tier that
|
|
161
|
-
// cannot. A classifier failure must not silently mute the agent.
|
|
162
|
-
assert.equal(replyTier(), "work");
|
|
163
|
-
assert.equal(replyTier(null), "work");
|
|
164
|
-
assert.equal(replyTier({}), "work");
|
|
165
|
-
assert.equal(replyTier({ action: 12, priority: [], rung: "x", willSpawnSession: "yes" }), "work");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("the tier is a pure function — same input, same answer, no ambient reads", () => {
|
|
169
|
-
const args = { answerable: false, action: "research", priority: "critical", rung: null, willSpawnSession: true };
|
|
170
|
-
const first = replyTier(args);
|
|
171
|
-
for (let i = 0; i < 50; i++) assert.equal(replyTier(args), first);
|
|
172
|
-
assert.deepEqual(args, { answerable: false, action: "research", priority: "critical", rung: null, willSpawnSession: true }, "the argument is not mutated");
|
|
173
|
-
});
|
|
174
|
-
});
|