@cohortapp/agent-sdk 2.3.0
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/commands/init-agent.md +104 -0
- package/.claude/commands/init-maestro.md +1187 -0
- package/.claude/settings.json +161 -0
- package/.env.example +216 -0
- package/README.md +632 -0
- package/agents/browser-operator/agent.md +52 -0
- package/agents/calendar-ops/agent.md +50 -0
- package/agents/communications/agent.md +96 -0
- package/agents/decision-log/agent.md +65 -0
- package/agents/desktop-operator/agent.md +59 -0
- package/agents/gmail-operator/agent.md +62 -0
- package/agents/inbound-dispatcher/agent.md +66 -0
- package/agents/inbox-processor/agent.md +39 -0
- package/agents/pmo-execution/agent.md +60 -0
- package/agents/session-spawner/agent.md +64 -0
- package/agents/slack-operator/agent.md +60 -0
- package/agents/whatsapp-operator/agent.md +60 -0
- package/agents/workflow-automation/agent.md +61 -0
- package/archetypes/altitudes/c-suite.yaml +58 -0
- package/archetypes/altitudes/founder.yaml +68 -0
- package/archetypes/altitudes/senior-manager.yaml +63 -0
- package/archetypes/altitudes/svp.yaml +60 -0
- package/archetypes/altitudes/vp.yaml +50 -0
- package/archetypes/archetype.schema.json +77 -0
- package/archetypes/base.yaml +47 -0
- package/archetypes/capabilities/commercial-leader.yaml +159 -0
- package/archetypes/capabilities/compliance-officer.yaml +159 -0
- package/archetypes/capabilities/executive-operator.yaml +169 -0
- package/archetypes/capabilities/finance-leader.yaml +162 -0
- package/archetypes/capabilities/operations-leader.yaml +154 -0
- package/archetypes/capabilities/product-leader.yaml +148 -0
- package/archetypes/capabilities/technical-leader.yaml +146 -0
- package/archetypes/functions/commercial-leader.yaml +62 -0
- package/archetypes/functions/compliance-officer.yaml +64 -0
- package/archetypes/functions/executive-operator.yaml +70 -0
- package/archetypes/functions/finance-leader.yaml +70 -0
- package/archetypes/functions/operations-leader.yaml +62 -0
- package/archetypes/functions/product-leader.yaml +61 -0
- package/archetypes/functions/technical-leader.yaml +57 -0
- package/bin/cohort-mcp.mjs +81 -0
- package/bin/maestro.mjs +3516 -0
- package/bin/maestro.test.mjs +1015 -0
- package/desktop-control/README.md +56 -0
- package/desktop-control/app-profiles/gmail.yaml +120 -0
- package/desktop-control/app-profiles/slack.yaml +315 -0
- package/desktop-control/app-profiles/whatsapp.yaml +107 -0
- package/docs/architecture/agent-topology.md +2239 -0
- package/docs/architecture/archetype-agent-factory.md +110 -0
- package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
- package/docs/architecture/continuous-monitoring.md +221 -0
- package/docs/architecture/mcp-capability-map.md +585 -0
- package/docs/architecture/system-architecture.md +1272 -0
- package/docs/company-context/README.md +40 -0
- package/docs/guides/agent-persona-setup.md +600 -0
- package/docs/guides/agents-observe-setup.md +64 -0
- package/docs/guides/billing-console-keys.md +88 -0
- package/docs/guides/ccxray-diagnostics.md +65 -0
- package/docs/guides/channel-bus.md +127 -0
- package/docs/guides/claude-mem-setup.md +79 -0
- package/docs/guides/claude-pace-setup.md +56 -0
- package/docs/guides/claudraband-sessions.md +98 -0
- package/docs/guides/clawteam-swarm.md +116 -0
- package/docs/guides/code-review-graph-setup.md +86 -0
- package/docs/guides/email-setup.md +431 -0
- package/docs/guides/mac-mini.md +119 -0
- package/docs/guides/media-generation-setup.md +349 -0
- package/docs/guides/model-routing.md +162 -0
- package/docs/guides/observability-otel.md +265 -0
- package/docs/guides/org-onboarding.md +132 -0
- package/docs/guides/outbound-governance-setup.md +437 -0
- package/docs/guides/pdf-generation-setup.md +315 -0
- package/docs/guides/poller-daemon-setup.md +563 -0
- package/docs/guides/rag-context-setup.md +459 -0
- package/docs/guides/self-optimization-pattern.md +82 -0
- package/docs/guides/setup-wizard.md +178 -0
- package/docs/guides/slack-setup.md +350 -0
- package/docs/guides/telegram-setup.md +227 -0
- package/docs/guides/twilio-subaccounts-setup.md +223 -0
- package/docs/guides/verification.md +128 -0
- package/docs/guides/voice-mode.md +188 -0
- package/docs/guides/voice-sms-setup.md +698 -0
- package/docs/guides/webhook-relay-setup.md +349 -0
- package/docs/guides/whatsapp-setup.md +288 -0
- package/docs/prompts/board-pack-cover-template.md +36 -0
- package/docs/prompts/decision-recommendation-template.md +88 -0
- package/docs/prompts/followup-message-template.md +141 -0
- package/docs/prompts/investor-letter-template.md +52 -0
- package/docs/prompts/morning-brief-template.md +82 -0
- package/docs/prompts/presentation-template.md +58 -0
- package/docs/prompts/weekly-strategic-memo-template.md +104 -0
- package/docs/research/hallucinated-tool-output-investigation.md +151 -0
- package/docs/runbooks/backup-restore.md +205 -0
- package/docs/runbooks/cohort-cutover.md +129 -0
- package/docs/runbooks/fleet-operations.md +200 -0
- package/docs/runbooks/incident-response.md +226 -0
- package/docs/runbooks/mac-mini-bootstrap.md +431 -0
- package/docs/runbooks/perpetual-operations.md +509 -0
- package/docs/runbooks/recovery-and-failover.md +260 -0
- package/framework-features.json +267 -0
- package/ingest/README.md +87 -0
- package/lib/action-executor.js +689 -0
- package/lib/action-executor.test.mjs +871 -0
- package/lib/agent-root.mjs +37 -0
- package/lib/archetype.mjs +236 -0
- package/lib/archetype.test.mjs +132 -0
- package/lib/autonomy.mjs +114 -0
- package/lib/autonomy.test.mjs +66 -0
- package/lib/backlog.mjs +358 -0
- package/lib/backlog.test.mjs +266 -0
- package/lib/budget-guard.mjs +279 -0
- package/lib/budget-guard.test.mjs +291 -0
- package/lib/cadence-bus-schedule.test.mjs +194 -0
- package/lib/cadence-bus.mjs +1120 -0
- package/lib/cadence-bus.test.mjs +720 -0
- package/lib/cadences.mjs +205 -0
- package/lib/cadences.test.mjs +125 -0
- package/lib/capability.mjs +154 -0
- package/lib/capability.test.mjs +78 -0
- package/lib/channels/base-adapter.mjs +719 -0
- package/lib/channels/base-adapter.test.mjs +590 -0
- package/lib/channels/channel.mjs +128 -0
- package/lib/channels/channels.test.mjs +371 -0
- package/lib/channels/contract.mjs +215 -0
- package/lib/channels/contract.test.mjs +137 -0
- package/lib/channels/conversation-resolver.mjs +95 -0
- package/lib/channels/gmail/adapter.mjs +87 -0
- package/lib/channels/inbox-item.mjs +255 -0
- package/lib/channels/inbox-item.test.mjs +335 -0
- package/lib/channels/index.mjs +94 -0
- package/lib/channels/orgmail/adapter.mjs +353 -0
- package/lib/channels/orgmail/adapter.test.mjs +311 -0
- package/lib/channels/pairing.mjs +363 -0
- package/lib/channels/pairing.test.mjs +270 -0
- package/lib/channels/registry.mjs +164 -0
- package/lib/channels/slack/adapter.mjs +317 -0
- package/lib/channels/slack-adapter.test.mjs +212 -0
- package/lib/channels/sms/adapter.mjs +43 -0
- package/lib/channels/telegram/adapter.mjs +432 -0
- package/lib/channels/telegram-adapter.test.mjs +306 -0
- package/lib/channels/voice/adapter.mjs +301 -0
- package/lib/channels/voice/adapter.test.mjs +278 -0
- package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
- package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
- package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
- package/lib/charter.mjs +256 -0
- package/lib/charter.test.mjs +89 -0
- package/lib/claude-bin.mjs +134 -0
- package/lib/claude-bin.test.mjs +75 -0
- package/lib/collective/capture.mjs +185 -0
- package/lib/collective/capture.test.mjs +121 -0
- package/lib/collective/cards.mjs +201 -0
- package/lib/collective/cards.test.mjs +114 -0
- package/lib/collective/config.mjs +186 -0
- package/lib/collective/config.test.mjs +123 -0
- package/lib/collective/global-config.mjs +113 -0
- package/lib/collective/global-config.test.mjs +75 -0
- package/lib/collective/presence.mjs +201 -0
- package/lib/collective/presence.test.mjs +95 -0
- package/lib/collective/recall.mjs +215 -0
- package/lib/collective/recall.test.mjs +116 -0
- package/lib/comms/send-gate.mjs +554 -0
- package/lib/comms/send-gate.test.mjs +577 -0
- package/lib/comms.mjs +67 -0
- package/lib/comms.test.mjs +41 -0
- package/lib/diagnostics/alerts.mjs +424 -0
- package/lib/diagnostics/alerts.test.mjs +318 -0
- package/lib/diagnostics/backup-freshness.mjs +188 -0
- package/lib/diagnostics/backup-freshness.test.mjs +185 -0
- package/lib/diagnostics/counters.mjs +269 -0
- package/lib/diagnostics/counters.test.mjs +206 -0
- package/lib/diagnostics/events.mjs +188 -0
- package/lib/diagnostics/events.test.mjs +290 -0
- package/lib/diagnostics/otel.mjs +237 -0
- package/lib/diagnostics/otel.test.mjs +196 -0
- package/lib/diagnostics/trace.mjs +216 -0
- package/lib/diagnostics/trace.test.mjs +251 -0
- package/lib/env-compat.mjs +74 -0
- package/lib/env-compat.test.mjs +104 -0
- package/lib/feature-init.mjs +331 -0
- package/lib/fs-atomic.mjs +112 -0
- package/lib/fs-atomic.test.mjs +72 -0
- package/lib/fs-ownership.mjs +111 -0
- package/lib/fs-ownership.test.mjs +158 -0
- package/lib/hooks/bus.mjs +347 -0
- package/lib/hooks/bus.test.mjs +387 -0
- package/lib/index.js +16 -0
- package/lib/learning/config.mjs +106 -0
- package/lib/learning/config.test.mjs +75 -0
- package/lib/learning/counters.mjs +156 -0
- package/lib/learning/counters.test.mjs +69 -0
- package/lib/learning/curator-consolidate.test.mjs +238 -0
- package/lib/learning/curator.mjs +453 -0
- package/lib/learning/curator.test.mjs +106 -0
- package/lib/learning/log.mjs +40 -0
- package/lib/learning/reflect.mjs +534 -0
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.mjs +352 -0
- package/lib/learning/session-index.test.mjs +125 -0
- package/lib/learning/skill-writer.mjs +474 -0
- package/lib/learning/skill-writer.test.mjs +210 -0
- package/lib/mcp/server.mjs +328 -0
- package/lib/mcp/server.test.mjs +400 -0
- package/lib/model-router/auth-profiles.mjs +758 -0
- package/lib/model-router/auth-profiles.test.mjs +580 -0
- package/lib/model-router/catalog/anthropic.yaml +153 -0
- package/lib/model-router/catalog/deepseek.yaml +86 -0
- package/lib/model-router/catalog/moonshot.yaml +81 -0
- package/lib/model-router/catalog/qwen.yaml +114 -0
- package/lib/model-router/catalog.mjs +925 -0
- package/lib/model-router/catalog.test.mjs +385 -0
- package/lib/model-router/economics.mjs +564 -0
- package/lib/model-router/economics.test.mjs +344 -0
- package/lib/model-router/failover.mjs +298 -0
- package/lib/model-router/failover.test.mjs +439 -0
- package/lib/model-router/health.mjs +453 -0
- package/lib/model-router/health.test.mjs +338 -0
- package/lib/model-router/integration-coverage.test.mjs +829 -0
- package/lib/model-router/integration.test.mjs +564 -0
- package/lib/model-router/ledger.mjs +402 -0
- package/lib/model-router/ledger.test.mjs +382 -0
- package/lib/model-router/llm-task.mjs +515 -0
- package/lib/model-router/llm-task.test.mjs +392 -0
- package/lib/model-router/org-credentials.mjs +260 -0
- package/lib/model-router/org-credentials.test.mjs +265 -0
- package/lib/model-router/pricing-refresh.mjs +463 -0
- package/lib/model-router/pricing-refresh.test.mjs +286 -0
- package/lib/model-router/reconcile.mjs +429 -0
- package/lib/model-router/reconcile.test.mjs +316 -0
- package/lib/model-router/repair.mjs +471 -0
- package/lib/model-router/repair.test.mjs +180 -0
- package/lib/model-router/resolve.mjs +1206 -0
- package/lib/model-router/spawn.mjs +497 -0
- package/lib/model-router/spawn.test.mjs +425 -0
- package/lib/model-router/taxonomy.mjs +893 -0
- package/lib/model-router/taxonomy.test.mjs +410 -0
- package/lib/model-router.mjs +677 -0
- package/lib/model-router.test.mjs +907 -0
- package/lib/org/activity.mjs +211 -0
- package/lib/org/activity.test.mjs +134 -0
- package/lib/org/approvals.mjs +448 -0
- package/lib/org/approvals.test.mjs +216 -0
- package/lib/org/awareness.mjs +222 -0
- package/lib/org/awareness.test.mjs +159 -0
- package/lib/org/board.mjs +229 -0
- package/lib/org/board.test.mjs +177 -0
- package/lib/org/bootstrap-context.mjs +169 -0
- package/lib/org/bootstrap-context.test.mjs +153 -0
- package/lib/org/client.mjs +1628 -0
- package/lib/org/client.test.mjs +1107 -0
- package/lib/org/cohort-client.mjs +67 -0
- package/lib/org/cohort-client.test.mjs +126 -0
- package/lib/org/cost-sync.mjs +227 -0
- package/lib/org/cost-sync.test.mjs +153 -0
- package/lib/org/doctor.mjs +212 -0
- package/lib/org/doctor.test.mjs +212 -0
- package/lib/org/handoff.mjs +293 -0
- package/lib/org/handoff.test.mjs +269 -0
- package/lib/org/integration-tools.mjs +182 -0
- package/lib/org/integration-tools.test.mjs +160 -0
- package/lib/org/keys.mjs +131 -0
- package/lib/org/keys.test.mjs +92 -0
- package/lib/org/knowledge.mjs +463 -0
- package/lib/org/knowledge.test.mjs +319 -0
- package/lib/org/leases.mjs +335 -0
- package/lib/org/leases.test.mjs +235 -0
- package/lib/org/mesh-integration.test.mjs +127 -0
- package/lib/org/mesh.mjs +459 -0
- package/lib/org/mesh.test.mjs +345 -0
- package/lib/org/messaging.mjs +503 -0
- package/lib/org/messaging.test.mjs +238 -0
- package/lib/org/policy.mjs +345 -0
- package/lib/org/policy.test.mjs +237 -0
- package/lib/org/protocol.checksum +1 -0
- package/lib/org/protocol.checksum.test.mjs +90 -0
- package/lib/org/protocol.mjs +967 -0
- package/lib/org/protocol.test.mjs +264 -0
- package/lib/org/registry.mjs +194 -0
- package/lib/org/registry.test.mjs +100 -0
- package/lib/org/tool-surface-integration.test.mjs +120 -0
- package/lib/org/tool-surface.mjs +2535 -0
- package/lib/org/tool-surface.test.mjs +589 -0
- package/lib/org/ui-parity.mjs +3236 -0
- package/lib/org/ui-parity.test.mjs +348 -0
- package/lib/org/verify.mjs +176 -0
- package/lib/org/verify.test.mjs +194 -0
- package/lib/rag/embed.mjs +188 -0
- package/lib/rag/indexer.mjs +425 -0
- package/lib/rag/rag.test.mjs +505 -0
- package/lib/rag/search.mjs +475 -0
- package/lib/rate-guard.mjs +246 -0
- package/lib/rate-guard.test.mjs +201 -0
- package/lib/render.mjs +112 -0
- package/lib/render.test.mjs +68 -0
- package/lib/resource-governor.mjs +297 -0
- package/lib/resource-governor.test.mjs +262 -0
- package/lib/scheduling/dynamic-jobs.mjs +675 -0
- package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
- package/lib/scheduling/jitter.mjs +0 -0
- package/lib/scheduling/jitter.test.mjs +140 -0
- package/lib/secrets/broker.mjs +315 -0
- package/lib/secrets/broker.test.mjs +280 -0
- package/lib/secrets/providers.mjs +461 -0
- package/lib/secrets/providers.test.mjs +274 -0
- package/lib/security/audit-engine.mjs +684 -0
- package/lib/security/audit-engine.test.mjs +389 -0
- package/lib/security/coerce-args.mjs +552 -0
- package/lib/security/coerce-args.test.mjs +281 -0
- package/lib/security/dangerous-tools.mjs +97 -0
- package/lib/security/dangerous-tools.test.mjs +68 -0
- package/lib/security/external-content.mjs +145 -0
- package/lib/security/external-content.test.mjs +67 -0
- package/lib/security/redact.mjs +592 -0
- package/lib/security/redact.test.mjs +441 -0
- package/lib/security/secret-equal.mjs +73 -0
- package/lib/security/secret-equal.test.mjs +55 -0
- package/lib/session-permissions.mjs +101 -0
- package/lib/session-permissions.test.mjs +100 -0
- package/lib/setup/claude-probe.mjs +74 -0
- package/lib/setup/completeness.mjs +175 -0
- package/lib/setup/completeness.test.mjs +110 -0
- package/lib/setup/context-pack.mjs +173 -0
- package/lib/setup/context-pack.test.mjs +89 -0
- package/lib/setup/enrich.mjs +277 -0
- package/lib/setup/enrich.test.mjs +115 -0
- package/lib/setup/enroll-from-cohort.mjs +441 -0
- package/lib/setup/enroll-from-cohort.test.mjs +233 -0
- package/lib/setup/integration.test.mjs +162 -0
- package/lib/setup/io.mjs +360 -0
- package/lib/setup/io.test.mjs +77 -0
- package/lib/setup/run-generator.mjs +81 -0
- package/lib/setup/runner.mjs +244 -0
- package/lib/setup/runner.test.mjs +132 -0
- package/lib/setup/sections/comms.mjs +173 -0
- package/lib/setup/sections/company.mjs +120 -0
- package/lib/setup/sections/enrich.mjs +138 -0
- package/lib/setup/sections/identity.mjs +182 -0
- package/lib/setup/sections/identity.test.mjs +140 -0
- package/lib/setup/sections/learning.mjs +153 -0
- package/lib/setup/sections/learning.test.mjs +81 -0
- package/lib/setup/sections/messaging.mjs +219 -0
- package/lib/setup/sections/messaging.test.mjs +127 -0
- package/lib/setup/sections/model.mjs +102 -0
- package/lib/setup/sections/operating-model.mjs +78 -0
- package/lib/setup/sections/org.mjs +475 -0
- package/lib/setup/sections/org.test.mjs +313 -0
- package/lib/setup/sections/orgmail.mjs +173 -0
- package/lib/setup/sections/orgmail.test.mjs +118 -0
- package/lib/setup/sections/recovery.mjs +159 -0
- package/lib/setup/sections/recovery.test.mjs +98 -0
- package/lib/setup/sections/tools.mjs +132 -0
- package/lib/setup/sections/verify.mjs +97 -0
- package/lib/setup/sot.mjs +205 -0
- package/lib/setup/sot.test.mjs +81 -0
- package/lib/setup/state.mjs +151 -0
- package/lib/setup/state.test.mjs +92 -0
- package/lib/singleton.js +229 -0
- package/lib/singleton.test.mjs +135 -0
- package/lib/telemetry/alerts.mjs +216 -0
- package/lib/telemetry/alerts.test.mjs +109 -0
- package/lib/telemetry/collect.mjs +512 -0
- package/lib/telemetry/collect.test.mjs +202 -0
- package/lib/tool-definitions-integration.test.mjs +83 -0
- package/lib/tool-definitions.js +738 -0
- package/lib/tool-definitions.test.mjs +437 -0
- package/lib/util/fetch-timeout.mjs +136 -0
- package/lib/util/fetch-timeout.test.mjs +202 -0
- package/lib/util/reconnect.mjs +343 -0
- package/lib/util/reconnect.test.mjs +369 -0
- package/lib/util/unhandled.mjs +205 -0
- package/lib/util/unhandled.test.mjs +216 -0
- package/lib/voice/context-loader.mjs +466 -0
- package/lib/voice/index.mjs +100 -0
- package/lib/voice/openai-realtime.mjs +510 -0
- package/lib/voice/outbound.mjs +542 -0
- package/lib/voice/outbound.test.mjs +69 -0
- package/lib/voice/post-call-brief.mjs +428 -0
- package/lib/voice/provider.mjs +52 -0
- package/lib/voice/session-rotation.mjs +257 -0
- package/lib/voice/stt.mjs +161 -0
- package/lib/voice/stt.test.mjs +226 -0
- package/lib/voice/tool-bridge.mjs +370 -0
- package/lib/voice/tts.mjs +104 -0
- package/lib/voice/twilio-sip-bridge.mjs +288 -0
- package/lib/voice/voice.test.mjs +990 -0
- package/mcp/README.md +80 -0
- package/package.json +151 -0
- package/plugins/maestro-skills/plugin.json +139 -0
- package/plugins/maestro-skills/skills/agents-observe.md +110 -0
- package/plugins/maestro-skills/skills/board-deck.md +68 -0
- package/plugins/maestro-skills/skills/books-close.md +77 -0
- package/plugins/maestro-skills/skills/brand-steward.md +121 -0
- package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
- package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
- package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
- package/plugins/maestro-skills/skills/claude-pace.md +61 -0
- package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
- package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
- package/plugins/maestro-skills/skills/decision-brief.md +89 -0
- package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
- package/plugins/maestro-skills/skills/draft-comms.md +84 -0
- package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
- package/plugins/maestro-skills/skills/files-find.md +65 -0
- package/plugins/maestro-skills/skills/generative-ui.md +228 -0
- package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
- package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
- package/plugins/maestro-skills/skills/mail-triage.md +86 -0
- package/plugins/maestro-skills/skills/morning-brief.md +54 -0
- package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
- package/plugins/maestro-skills/skills/org-board.md +133 -0
- package/plugins/maestro-skills/skills/org-credential.md +68 -0
- package/plugins/maestro-skills/skills/org-recall.md +81 -0
- package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
- package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
- package/plugins/maestro-skills/skills/router-why.md +78 -0
- package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
- package/plugins/maestro-skills/skills/session-search.md +71 -0
- package/plugins/maestro-skills/skills/set-reminder.md +93 -0
- package/plugins/maestro-skills/skills/slack-followup.md +64 -0
- package/plugins/maestro-skills/skills/team-activity.md +86 -0
- package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
- package/policies/action-classification.yaml +114 -0
- package/policies/ai-disclosure.yaml +294 -0
- package/policies/communication-style.md +139 -0
- package/policies/information-barriers.yaml +118 -0
- package/policies/prompt-injection-defence.yaml +138 -0
- package/public/assets/icon-dark.png +0 -0
- package/public/assets/icon-dark.svg +9 -0
- package/public/assets/icon-light.svg +9 -0
- package/public/assets/logo-dark.svg +15 -0
- package/public/assets/logo-light.svg +15 -0
- package/scaffold/.mcp.json +7 -0
- package/scaffold/CLAUDE.md +368 -0
- package/scaffold/config/agent.json +55 -0
- package/scaffold/config/agent.ts +76 -0
- package/scaffold/config/agent.ts.example +89 -0
- package/scaffold/config/alerts.yaml +23 -0
- package/scaffold/config/allowlist.yaml.example +25 -0
- package/scaffold/config/caller-id-map.yaml +46 -0
- package/scaffold/config/collective.yaml +49 -0
- package/scaffold/config/company.json +20 -0
- package/scaffold/config/known-agents.json +6 -0
- package/scaffold/config/learning.yaml +55 -0
- package/scaffold/config/model-routing.yaml.example +104 -0
- package/scaffold/config/org.yaml +25 -0
- package/scaffold/config/orgmail.yaml.example +19 -0
- package/scaffold/config/recovery.yaml +72 -0
- package/scaffold/config/secrets.yaml +27 -0
- package/scaffold/config/slack.yaml.example +35 -0
- package/scaffold/config/telegram.yaml.example +38 -0
- package/scaffold/config/voice.yaml.example +89 -0
- package/scaffold/config/whatsapp.yaml.example +39 -0
- package/schedules/README.md +49 -0
- package/schedules/triggers/backlog-executor.md +102 -0
- package/schedules/triggers/brand-steward.md +72 -0
- package/schedules/triggers/daily-evening-wrap.md +159 -0
- package/schedules/triggers/daily-midday-sweep.md +58 -0
- package/schedules/triggers/daily-morning-brief.md +55 -0
- package/schedules/triggers/directory-hygiene.md +81 -0
- package/schedules/triggers/dynamic-jobs.md +40 -0
- package/schedules/triggers/inbox-processor.md +115 -0
- package/schedules/triggers/meeting-action-capture.md +60 -0
- package/schedules/triggers/meeting-prep.md +69 -0
- package/schedules/triggers/messaging-inbound.md +50 -0
- package/schedules/triggers/org-pulse.md +24 -0
- package/schedules/triggers/quarterly-self-assessment.md +54 -0
- package/schedules/triggers/weekly-engineering-health.md +37 -0
- package/schedules/triggers/weekly-execution.md +65 -0
- package/schedules/triggers/weekly-hiring.md +53 -0
- package/schedules/triggers/weekly-priorities.md +38 -0
- package/schedules/triggers/weekly-strategic-memo.md +124 -0
- package/scripts/archive-email.sh +55 -0
- package/scripts/cadence/cadence-status.mjs +36 -0
- package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
- package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
- package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
- package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
- package/scripts/ci/check-docs-accuracy.mjs +493 -0
- package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
- package/scripts/ci/check-exports-exist.mjs +140 -0
- package/scripts/ci/check-files-exist.mjs +107 -0
- package/scripts/ci/check-no-build-artifacts.mjs +111 -0
- package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
- package/scripts/ci/check-no-confidential.mjs +198 -0
- package/scripts/ci/check-no-conflict-markers.mjs +169 -0
- package/scripts/ci/check-no-residual-identity.mjs +163 -0
- package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
- package/scripts/ci/check-tarball-fidelity.mjs +205 -0
- package/scripts/ci/check-unresolved-tokens.mjs +83 -0
- package/scripts/ci/check.mjs +109 -0
- package/scripts/ci/check.test.mjs +194 -0
- package/scripts/ci/run-coverage.mjs +82 -0
- package/scripts/ci/run-tests.mjs +71 -0
- package/scripts/cloud-relay/README.md +59 -0
- package/scripts/cloud-relay/index.mjs +233 -0
- package/scripts/cloud-relay/package.json +15 -0
- package/scripts/cloud-relay/railway.json +13 -0
- package/scripts/cloud-relay/voice/README.md +94 -0
- package/scripts/cloud-relay/voice/package-lock.json +39 -0
- package/scripts/cloud-relay/voice/package.json +16 -0
- package/scripts/cloud-relay/voice/railway.json +13 -0
- package/scripts/cloud-relay/voice/server.mjs +532 -0
- package/scripts/collective/hook-runner.mjs +211 -0
- package/scripts/collective/hook-runner.test.mjs +90 -0
- package/scripts/collective/org-pulse.mjs +72 -0
- package/scripts/collective/org-sync.mjs +61 -0
- package/scripts/collective/recall.mjs +45 -0
- package/scripts/collective/who.mjs +30 -0
- package/scripts/comms-monitor.sh +288 -0
- package/scripts/configure-whatsapp-sandbox.sh +201 -0
- package/scripts/continuous-monitor.sh +91 -0
- package/scripts/cost/fleet-digest.mjs +407 -0
- package/scripts/cost/fleet-digest.test.mjs +207 -0
- package/scripts/cost/track-claude-usage.mjs +169 -0
- package/scripts/daemon/agent-daemon.mjs +989 -0
- package/scripts/daemon/agent-daemon.test.mjs +525 -0
- package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
- package/scripts/daemon/cadence-consumer.mjs +1080 -0
- package/scripts/daemon/cadence-consumer.test.mjs +770 -0
- package/scripts/daemon/cadence-handlers.mjs +1121 -0
- package/scripts/daemon/cadence-handlers.test.mjs +617 -0
- package/scripts/daemon/classifier.mjs +704 -0
- package/scripts/daemon/classifier.test.mjs +238 -0
- package/scripts/daemon/classify-kind.mjs +54 -0
- package/scripts/daemon/classify-kind.test.mjs +40 -0
- package/scripts/daemon/context-compiler.mjs +605 -0
- package/scripts/daemon/context-compiler.test.mjs +300 -0
- package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
- package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
- package/scripts/daemon/dispatcher.mjs +1516 -0
- package/scripts/daemon/health.mjs +72 -0
- package/scripts/daemon/inbox-deferral.mjs +210 -0
- package/scripts/daemon/inbox-deferral.test.mjs +242 -0
- package/scripts/daemon/integration.test.mjs +149 -0
- package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
- package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
- package/scripts/daemon/launchd-wrapper.sh +91 -0
- package/scripts/daemon/lib/session-router.mjs +274 -0
- package/scripts/daemon/lib/session-router.test.mjs +295 -0
- package/scripts/daemon/maestro-daemon.mjs +275 -0
- package/scripts/daemon/prompt-builder.mjs +685 -0
- package/scripts/daemon/prompt-builder.test.mjs +213 -0
- package/scripts/daemon/responder.mjs +854 -0
- package/scripts/daemon/session-lock.mjs +721 -0
- package/scripts/daemon/session-lock.test.mjs +252 -0
- package/scripts/daemon/session-outcomes.mjs +640 -0
- package/scripts/daemon/session-outcomes.test.mjs +533 -0
- package/scripts/daemon/typing-registry.mjs +90 -0
- package/scripts/daemon/typing-registry.test.mjs +77 -0
- package/scripts/daemon/voice-webhook-server.mjs +804 -0
- package/scripts/decisions/capture-decision.mjs +116 -0
- package/scripts/disclosure_assessment.py +873 -0
- package/scripts/disclosure_boundaries.py +562 -0
- package/scripts/email-signature-principal.html +52 -0
- package/scripts/email-signature.html +60 -0
- package/scripts/email_quote_thread.py +167 -0
- package/scripts/email_thread_dedup.py +362 -0
- package/scripts/emergency-stop.sh +81 -0
- package/scripts/healthcheck.sh +116 -0
- package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
- package/scripts/hooks/block-mcp-slack-send.sh +7 -0
- package/scripts/hooks/post-action-log.sh +126 -0
- package/scripts/hooks/pre-send-audit.sh +174 -0
- package/scripts/hooks/pre-send-audit.test.mjs +215 -0
- package/scripts/hooks/session-end-log.sh +27 -0
- package/scripts/hooks/session-start-banner.sh +115 -0
- package/scripts/huddle/audio-bridge.mjs +664 -0
- package/scripts/huddle/boot-slack-cdp.sh +102 -0
- package/scripts/huddle/huddle-controller.mjs +942 -0
- package/scripts/huddle/huddle-server.mjs +1229 -0
- package/scripts/huddle/launch-slack.sh +232 -0
- package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
- package/scripts/huddle/package-lock.json +62 -0
- package/scripts/huddle/package.json +22 -0
- package/scripts/huddle/setup-audio.sh +239 -0
- package/scripts/huddle/start-call.mjs +318 -0
- package/scripts/huddle/test-pipeline.mjs +263 -0
- package/scripts/learning/consolidate-skills.mjs +72 -0
- package/scripts/learning/session-search.mjs +125 -0
- package/scripts/llm_email_dedup.py +442 -0
- package/scripts/local-triggers/generate-plists.sh +432 -0
- package/scripts/local-triggers/generate-plists.test.mjs +413 -0
- package/scripts/local-triggers/install-all.sh +49 -0
- package/scripts/local-triggers/plists/.gitkeep +0 -0
- package/scripts/local-triggers/run-trigger.sh +63 -0
- package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
- package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
- package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
- package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
- package/scripts/maintenance/backup-to-cloud.sh +124 -0
- package/scripts/maintenance/health-check.sh +377 -0
- package/scripts/media-generation/README.md +105 -0
- package/scripts/media-generation/gemini-image-client.mjs +173 -0
- package/scripts/media-generation/generate-assets.mjs +289 -0
- package/scripts/media-generation/veo-video-client.mjs +219 -0
- package/scripts/org/send-orgmail.mjs +227 -0
- package/scripts/outbound-dedup-cleanup.sh +43 -0
- package/scripts/outbound-dedup.sh +477 -0
- package/scripts/outbound_dedup.py +115 -0
- package/scripts/parse-voice-transcript.mjs +481 -0
- package/scripts/pdf-generation/README.md +63 -0
- package/scripts/pdf-generation/build-document.mjs +247 -0
- package/scripts/pdf-generation/templates/board-pack.latex +136 -0
- package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
- package/scripts/pdf-generation/templates/memo.latex +114 -0
- package/scripts/poll-slack-events.sh +35 -0
- package/scripts/poller/calendar-poller.mjs +12 -0
- package/scripts/poller/gmail-poller.mjs +192 -0
- package/scripts/poller/imap-client.mjs +289 -0
- package/scripts/poller/inbox-scan-poller.mjs +156 -0
- package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
- package/scripts/poller/index.mjs +73 -0
- package/scripts/poller/intra-session-check.mjs +285 -0
- package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
- package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
- package/scripts/poller/secondary-gmail-poller.mjs +132 -0
- package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
- package/scripts/poller/slack-poller.mjs +854 -0
- package/scripts/poller/slack-socket-mode.mjs +917 -0
- package/scripts/poller/slack-socket-mode.test.mjs +753 -0
- package/scripts/poller/trigger.mjs +75 -0
- package/scripts/poller/utils.mjs +371 -0
- package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
- package/scripts/poller/voice-poller.mjs +236 -0
- package/scripts/poller-launchd/install.sh +66 -0
- package/scripts/poller-launchd/poller.plist.template +40 -0
- package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
- package/scripts/post-interaction-indexer.py +1598 -0
- package/scripts/pre-draft-context.py +994 -0
- package/scripts/pre_draft_lookup.py +258 -0
- package/scripts/rag/build-index.mjs +47 -0
- package/scripts/rag/ingest.mjs +111 -0
- package/scripts/rag/search.mjs +119 -0
- package/scripts/rag-indexer.py +629 -0
- package/scripts/restore-from-backup.sh +248 -0
- package/scripts/restore-from-backup.test.mjs +178 -0
- package/scripts/resume-operations.sh +80 -0
- package/scripts/search-secondary-inbox.py +181 -0
- package/scripts/secondary-inbox-poller.py +437 -0
- package/scripts/self-optimization/compute-metrics.py +398 -0
- package/scripts/send-email-as-principal.py +369 -0
- package/scripts/send-email-threaded.py +392 -0
- package/scripts/send-email-with-attachment.py +377 -0
- package/scripts/send-email.sh +131 -0
- package/scripts/send-sms.sh +175 -0
- package/scripts/send-whatsapp.sh +292 -0
- package/scripts/session-start.sh +106 -0
- package/scripts/setup/boot-claude-session.sh +94 -0
- package/scripts/setup/configure-macos.sh +674 -0
- package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
- package/scripts/setup/configure-voice-tunnel.mjs +182 -0
- package/scripts/setup/generate-agent-env.mjs +92 -0
- package/scripts/setup/generate-agent-package-json.mjs +222 -0
- package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
- package/scripts/setup/generate-autonomy.mjs +60 -0
- package/scripts/setup/generate-backlog.mjs +101 -0
- package/scripts/setup/generate-cadences.mjs +92 -0
- package/scripts/setup/generate-capability.mjs +162 -0
- package/scripts/setup/generate-charter.mjs +76 -0
- package/scripts/setup/generate-comms.mjs +59 -0
- package/scripts/setup/generate-company.mjs +92 -0
- package/scripts/setup/init-agent.sh +547 -0
- package/scripts/setup/init-agent.test.mjs +151 -0
- package/scripts/setup/init-archetype.mjs +74 -0
- package/scripts/setup/init-backup.mjs +54 -0
- package/scripts/setup/init-cadence-bus.mjs +60 -0
- package/scripts/setup/init-channel-bus.mjs +46 -0
- package/scripts/setup/init-cost-tracking.mjs +45 -0
- package/scripts/setup/init-decision-capture.mjs +66 -0
- package/scripts/setup/init-known-agents.mjs +57 -0
- package/scripts/setup/init-learning.mjs +70 -0
- package/scripts/setup/init-memory-executive.mjs +45 -0
- package/scripts/setup/init-model-router.mjs +124 -0
- package/scripts/setup/init-rag.mjs +174 -0
- package/scripts/setup/init-session-router.mjs +38 -0
- package/scripts/setup/init-slack-socket-mode.mjs +260 -0
- package/scripts/setup/init-telegram.mjs +165 -0
- package/scripts/setup/init-voice-realtime.mjs +204 -0
- package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
- package/scripts/setup/install-dev-tools.sh +150 -0
- package/scripts/setup/lib/install-plist.mjs +95 -0
- package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
- package/scripts/setup/render-environment-yaml.mjs +133 -0
- package/scripts/slack-events-ctl.sh +177 -0
- package/scripts/slack-events-server.mjs +1045 -0
- package/scripts/slack-react.mjs +89 -0
- package/scripts/slack-responded.sh +232 -0
- package/scripts/slack-send.sh +287 -0
- package/scripts/slack-typing.mjs +196 -0
- package/scripts/slack-upload-v2.py +95 -0
- package/scripts/sms-handler.mjs +450 -0
- package/scripts/spawn-session.sh +120 -0
- package/scripts/sync-protocol.mjs +217 -0
- package/scripts/system-verify.sh +184 -0
- package/scripts/test-email-thread-dedup.py +239 -0
- package/scripts/test-information-barriers.py +484 -0
- package/scripts/test-llm-email-dedup.py +251 -0
- package/scripts/test-pre-draft-integration.py +203 -0
- package/scripts/test-rag-phase2.sh +442 -0
- package/scripts/test-rag-search.sh +251 -0
- package/scripts/test-voice-parser.mjs +316 -0
- package/scripts/user-context-search.py +659 -0
- package/scripts/validate_outbound.py +1504 -0
- package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
- package/scripts/watchdog/force-reboot.sh +157 -0
- package/scripts/watchdog/memory-watchdog.sh +473 -0
- package/scripts/whatsapp-handler.mjs +538 -0
- package/teams/desktop-operations.yaml +34 -0
- package/teams/executive-office.yaml +27 -0
- package/teams/legal-and-regulatory.yaml +24 -0
- package/teams/platform-and-engineering.yaml +23 -0
- package/teams/strategy-and-growth.yaml +29 -0
- package/workflows/continuous/backlog-executor.yaml +141 -0
- package/workflows/continuous/inbound-monitor.yaml +168 -0
- package/workflows/daily/applicant-triage.yaml +197 -0
- package/workflows/daily/comms-triage.yaml +80 -0
- package/workflows/daily/evening-wrap.yaml +105 -0
- package/workflows/daily/morning-brief.yaml +164 -0
- package/workflows/daily/slack-followup-sweep.yaml +87 -0
- package/workflows/event-driven/README.md +50 -0
- package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
- package/workflows/event-driven/pr-review.yaml +107 -0
- package/workflows/monthly/board-readiness.yaml +76 -0
- package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
- package/workflows/session-protocol.md +171 -0
- package/workflows/weekly/engineering-health.yaml +154 -0
- package/workflows/weekly/hiring-review.yaml +169 -0
- package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
- package/workflows/weekly/strategic-memo.yaml +79 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* audit-engine.mjs — Registry-driven security-posture audit + hash-locked
|
|
3
|
+
* attestations (gaps-enterprise-ops G15).
|
|
4
|
+
*
|
|
5
|
+
* Doctor verifies *plumbing* (is the daemon wired, are queues healthy). This
|
|
6
|
+
* engine verifies *security posture* — the confirmed-CRITICAL/HIGH items from
|
|
7
|
+
* the enterprise audit that have a binary, machine-checkable answer on a single
|
|
8
|
+
* agent's disk: file modes on secrets, scoped-permission opt-in, the send-gate
|
|
9
|
+
* hook, no tracked build artifacts / symlinks, backup configured, an AI
|
|
10
|
+
* disclosure policy, and the daemon audit hooks being wired.
|
|
11
|
+
*
|
|
12
|
+
* Design mirrors lib/setup/sections/*.mjs and scripts/ci/check.mjs: a flat
|
|
13
|
+
* array of self-contained checks, each `{ id, severity, run(agentRoot, deps) }`.
|
|
14
|
+
* You extend the audit by adding a check to the array — never by editing a
|
|
15
|
+
* dispatcher. Every check is pure over its injected `fs`/`env`, so the whole
|
|
16
|
+
* suite runs in one process with no spawns and is trivially unit-testable.
|
|
17
|
+
*
|
|
18
|
+
* Fail-open is deliberately NOT the posture here: a check that cannot read the
|
|
19
|
+
* thing it audits returns `ok:false` with an explanatory detail, because "I
|
|
20
|
+
* couldn't verify the secret's mode" is itself a posture failure for an
|
|
21
|
+
* enterprise conformance artifact. (Contrast the runtime daemon, which fails
|
|
22
|
+
* open so a sensor glitch never bricks the agent.) The one exception is the
|
|
23
|
+
* suppression layer, which fails *safe*: a malformed suppressions file is
|
|
24
|
+
* ignored, never trusted to silence a finding.
|
|
25
|
+
*
|
|
26
|
+
* The attestation is a sha256 over the agent's policy/archetype identity plus
|
|
27
|
+
* the audit result, so the org server can aggregate a per-agent conformance
|
|
28
|
+
* artifact and detect drift: the hash is stable for an unchanged posture and
|
|
29
|
+
* changes the instant the policy (or any check outcome) changes.
|
|
30
|
+
*
|
|
31
|
+
* @module lib/security/audit-engine
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
"use strict";
|
|
35
|
+
|
|
36
|
+
import * as nodeFs from "node:fs";
|
|
37
|
+
import { join } from "node:path";
|
|
38
|
+
import { createHash } from "node:crypto";
|
|
39
|
+
|
|
40
|
+
// Severity ordering — used to decide which unsuppressed failures fail the run
|
|
41
|
+
// and to sort the summary. `info` never fails a run on its own.
|
|
42
|
+
export const SEVERITIES = Object.freeze(["info", "low", "medium", "high", "critical"]);
|
|
43
|
+
const SEVERITY_RANK = Object.freeze(
|
|
44
|
+
SEVERITIES.reduce((m, s, i) => ((m[s] = i), m), Object.create(null))
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// fs adapter — a minimal, injectable surface so tests pass a fake disk.
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build the dependency bundle a check sees. Tests pass `{ fs, env }` overrides;
|
|
53
|
+
* production uses node:fs and process.env. The adapter normalises the handful
|
|
54
|
+
* of fs calls the checks make so a fake only has to implement those.
|
|
55
|
+
*
|
|
56
|
+
* @param {{ fs?: object, env?: object }} [deps]
|
|
57
|
+
*/
|
|
58
|
+
function resolveDeps(deps = {}) {
|
|
59
|
+
const fs = deps.fs || nodeFs;
|
|
60
|
+
const env = deps.env || process.env;
|
|
61
|
+
return { fs, env };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** statSync that never throws — returns null when the path is absent/unreadable. */
|
|
65
|
+
function safeStat(fs, p) {
|
|
66
|
+
try { return fs.statSync(p); } catch { return null; }
|
|
67
|
+
}
|
|
68
|
+
/** lstatSync that never throws (used to detect symlinks without following them). */
|
|
69
|
+
function safeLstat(fs, p) {
|
|
70
|
+
try { return fs.lstatSync(p); } catch { return null; }
|
|
71
|
+
}
|
|
72
|
+
/** readFileSync(utf8) that never throws — returns null when absent/unreadable. */
|
|
73
|
+
function safeRead(fs, p) {
|
|
74
|
+
try { return fs.readFileSync(p, "utf-8"); } catch { return null; }
|
|
75
|
+
}
|
|
76
|
+
function exists(fs, p) {
|
|
77
|
+
try { return fs.existsSync(p); } catch { return false; }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Walk a directory tree (lstat-based, never follows symlinks) yielding entries
|
|
82
|
+
* `{ rel, full, isSymlink, isFile, isDir }`. Bounded and exception-safe so a
|
|
83
|
+
* single unreadable subtree can't abort the audit.
|
|
84
|
+
*/
|
|
85
|
+
function* walk(fs, root, base = root) {
|
|
86
|
+
let names;
|
|
87
|
+
try { names = fs.readdirSync(root); } catch { return; }
|
|
88
|
+
for (const name of names) {
|
|
89
|
+
const full = join(root, name);
|
|
90
|
+
const lst = safeLstat(fs, full);
|
|
91
|
+
if (!lst) continue;
|
|
92
|
+
const rel = full.slice(base.length + 1);
|
|
93
|
+
if (lst.isSymbolicLink()) { yield { rel, full, isSymlink: true, isFile: false, isDir: false }; continue; }
|
|
94
|
+
if (lst.isDirectory()) {
|
|
95
|
+
// Don't descend into node_modules / .git — they're never agent posture.
|
|
96
|
+
if (name === "node_modules" || name === ".git") continue;
|
|
97
|
+
yield { rel, full, isSymlink: false, isFile: false, isDir: true };
|
|
98
|
+
yield* walk(fs, full, base);
|
|
99
|
+
} else if (lst.isFile()) {
|
|
100
|
+
yield { rel, full, isSymlink: false, isFile: true, isDir: false };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Checks registry — each entry is self-contained. `run` returns
|
|
107
|
+
// `{ ok, detail, fix?, fixable?, applyFix? }`:
|
|
108
|
+
// ok — boolean posture verdict
|
|
109
|
+
// detail — one human line for the summary (and the attestation body)
|
|
110
|
+
// fix — the exact command/edit an operator should run (shown on fail)
|
|
111
|
+
// fixable — true if `maestro audit --fix` can safely auto-remediate
|
|
112
|
+
// applyFix(agentRoot, deps) — performs the safe fix; returns true on success
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
const MODE_MASK = 0o777;
|
|
116
|
+
|
|
117
|
+
/** Files whose plaintext contents are credentials — must be owner-only (0600). */
|
|
118
|
+
const SECRET_FILES = [".env", "config/agent.env"];
|
|
119
|
+
|
|
120
|
+
export const CHECKS = [
|
|
121
|
+
{
|
|
122
|
+
id: "env-mode-600",
|
|
123
|
+
severity: "high",
|
|
124
|
+
title: "Secret files are owner-only (mode 600)",
|
|
125
|
+
run(agentRoot, deps) {
|
|
126
|
+
const { fs } = resolveDeps(deps);
|
|
127
|
+
const offenders = [];
|
|
128
|
+
let checkedAny = false;
|
|
129
|
+
for (const rel of SECRET_FILES) {
|
|
130
|
+
const p = join(agentRoot, rel);
|
|
131
|
+
const st = safeStat(fs, p);
|
|
132
|
+
if (!st) continue; // file absent — not this check's concern
|
|
133
|
+
checkedAny = true;
|
|
134
|
+
const mode = st.mode & MODE_MASK;
|
|
135
|
+
// Owner-only: group + other bits must be clear (<= 0600 in practice).
|
|
136
|
+
if (mode & 0o077) offenders.push({ rel, mode });
|
|
137
|
+
}
|
|
138
|
+
if (!checkedAny) {
|
|
139
|
+
return { ok: true, detail: "no secret files present to check" };
|
|
140
|
+
}
|
|
141
|
+
if (offenders.length === 0) {
|
|
142
|
+
return { ok: true, detail: "all secret files are mode 600 (owner-only)" };
|
|
143
|
+
}
|
|
144
|
+
const list = offenders.map((o) => `${o.rel} (mode ${(o.mode).toString(8).padStart(3, "0")})`).join(", ");
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
detail: `group/world-readable secret file(s): ${list}`,
|
|
148
|
+
fix: `chmod 600 ${offenders.map((o) => o.rel).join(" ")}`,
|
|
149
|
+
fixable: true,
|
|
150
|
+
applyFix(root, d) {
|
|
151
|
+
const { fs } = resolveDeps(d);
|
|
152
|
+
let allOk = true;
|
|
153
|
+
for (const o of offenders) {
|
|
154
|
+
try { fs.chmodSync(join(root, o.rel), 0o600); } catch { allOk = false; }
|
|
155
|
+
}
|
|
156
|
+
return allOk;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
{
|
|
163
|
+
id: "no-world-readable-secrets",
|
|
164
|
+
severity: "high",
|
|
165
|
+
title: "No world-readable files under state/ contain credential-shaped values",
|
|
166
|
+
run(agentRoot, deps) {
|
|
167
|
+
const { fs } = resolveDeps(deps);
|
|
168
|
+
const stateDir = join(agentRoot, "state");
|
|
169
|
+
if (!exists(fs, stateDir)) return { ok: true, detail: "no state/ directory" };
|
|
170
|
+
// We flag files that are (a) group/world-readable AND (b) look like they
|
|
171
|
+
// carry secrets by name. We do NOT read+grep arbitrary state for tokens —
|
|
172
|
+
// that would itself be a data-exposure risk in the audit. Name-based is
|
|
173
|
+
// the confirmed posture signal (allowlist.yaml pairing codes, *.env, keys).
|
|
174
|
+
const SECRET_NAME = /(\.env$|secret|token|credential|\.key$|\.pem$|auth\.json$)/i;
|
|
175
|
+
const offenders = [];
|
|
176
|
+
for (const e of walk(fs, stateDir)) {
|
|
177
|
+
if (!e.isFile) continue;
|
|
178
|
+
if (!SECRET_NAME.test(e.rel)) continue;
|
|
179
|
+
const st = safeStat(fs, e.full);
|
|
180
|
+
if (!st) continue;
|
|
181
|
+
if ((st.mode & MODE_MASK) & 0o044) offenders.push(`state/${e.rel}`); // group/other READ bit
|
|
182
|
+
}
|
|
183
|
+
if (offenders.length === 0) {
|
|
184
|
+
return { ok: true, detail: "no world/group-readable secret-shaped files under state/" };
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
detail: `group/world-readable secret-shaped file(s): ${offenders.slice(0, 8).join(", ")}${offenders.length > 8 ? ` (+${offenders.length - 8})` : ""}`,
|
|
189
|
+
fix: `chmod go-rwx ${offenders.join(" ")}`,
|
|
190
|
+
fixable: true,
|
|
191
|
+
applyFix(root, d) {
|
|
192
|
+
const { fs } = resolveDeps(d);
|
|
193
|
+
let allOk = true;
|
|
194
|
+
for (const rel of offenders) {
|
|
195
|
+
try {
|
|
196
|
+
const p = join(root, rel);
|
|
197
|
+
const st = safeStat(fs, p);
|
|
198
|
+
if (st) fs.chmodSync(p, st.mode & ~0o077 & MODE_MASK);
|
|
199
|
+
} catch { allOk = false; }
|
|
200
|
+
}
|
|
201
|
+
return allOk;
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
{
|
|
208
|
+
id: "scoped-permissions-configured",
|
|
209
|
+
severity: "critical",
|
|
210
|
+
title: "Reactive inbound sessions run with scoped permissions",
|
|
211
|
+
run(agentRoot, deps) {
|
|
212
|
+
const { fs, env } = resolveDeps(deps);
|
|
213
|
+
// Scoped permissions are configured if EITHER the env opt-in is set OR the
|
|
214
|
+
// recovery/security config records it. The confirmed gap (G3) is that the
|
|
215
|
+
// default is unscoped; this check passes only when the opt-in is present.
|
|
216
|
+
if (String(env.MAESTRO_SCOPED_PERMISSIONS || "") === "1") {
|
|
217
|
+
return { ok: true, detail: "MAESTRO_SCOPED_PERMISSIONS=1 (scoped inbound sessions)" };
|
|
218
|
+
}
|
|
219
|
+
const envBody = safeRead(fs, join(agentRoot, ".env")) || "";
|
|
220
|
+
if (/^MAESTRO_SCOPED_PERMISSIONS=1\s*$/m.test(envBody)) {
|
|
221
|
+
return { ok: true, detail: "MAESTRO_SCOPED_PERMISSIONS=1 set in .env" };
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
detail: "scoped permissions not enabled — inbound sessions run with full tool access (G3)",
|
|
226
|
+
fix: "set MAESTRO_SCOPED_PERMISSIONS=1 in .env (opt into least-privilege inbound sessions)",
|
|
227
|
+
// Not auto-fixable: flipping permission posture is an operator decision,
|
|
228
|
+
// and a blind --fix that edits .env could surprise the operator.
|
|
229
|
+
fixable: false,
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
{
|
|
235
|
+
id: "send-gate-present",
|
|
236
|
+
severity: "high",
|
|
237
|
+
title: "Outbound send-gate hook is wired",
|
|
238
|
+
run(agentRoot, deps) {
|
|
239
|
+
const { fs } = resolveDeps(deps);
|
|
240
|
+
const settings = safeRead(fs, join(agentRoot, ".claude/settings.json"));
|
|
241
|
+
if (!settings) {
|
|
242
|
+
return {
|
|
243
|
+
ok: false,
|
|
244
|
+
detail: ".claude/settings.json missing — no PreToolUse send-gate",
|
|
245
|
+
fix: "run `maestro upgrade` to install .claude/settings.json with the send-gate hooks",
|
|
246
|
+
fixable: false,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
let cfg;
|
|
250
|
+
try { cfg = JSON.parse(settings); } catch {
|
|
251
|
+
return { ok: false, detail: ".claude/settings.json is not valid JSON", fix: "repair .claude/settings.json (run `maestro upgrade`)", fixable: false };
|
|
252
|
+
}
|
|
253
|
+
const preHooks = JSON.stringify((cfg.hooks && cfg.hooks.PreToolUse) || []);
|
|
254
|
+
const hasSlackGate = preHooks.includes("block-mcp-slack-send.sh");
|
|
255
|
+
const hasSendAudit = preHooks.includes("pre-send-audit.sh");
|
|
256
|
+
if (hasSlackGate && hasSendAudit) {
|
|
257
|
+
return { ok: true, detail: "PreToolUse send-gate wired (slack block + pre-send audit)" };
|
|
258
|
+
}
|
|
259
|
+
const missing = [];
|
|
260
|
+
if (!hasSlackGate) missing.push("block-mcp-slack-send.sh");
|
|
261
|
+
if (!hasSendAudit) missing.push("pre-send-audit.sh");
|
|
262
|
+
return {
|
|
263
|
+
ok: false,
|
|
264
|
+
detail: `PreToolUse send-gate incomplete — missing ${missing.join(", ")}`,
|
|
265
|
+
fix: "run `maestro upgrade` to restore the send-gate PreToolUse hooks",
|
|
266
|
+
fixable: false,
|
|
267
|
+
};
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
{
|
|
272
|
+
id: "no-tracked-pyc-or-symlinks",
|
|
273
|
+
severity: "medium",
|
|
274
|
+
title: "No tracked .pyc files or symlinks in framework paths",
|
|
275
|
+
run(agentRoot, deps) {
|
|
276
|
+
const { fs } = resolveDeps(deps);
|
|
277
|
+
// The confirmed leak (G4) shipped a compiled cross-tenant .pyc and a
|
|
278
|
+
// broken symlink in the npm tarball. Scan the framework source roots.
|
|
279
|
+
const roots = ["scripts", "lib", "config", "policies"];
|
|
280
|
+
const pyc = [];
|
|
281
|
+
const links = [];
|
|
282
|
+
for (const r of roots) {
|
|
283
|
+
const dir = join(agentRoot, r);
|
|
284
|
+
if (!exists(fs, dir)) continue;
|
|
285
|
+
for (const e of walk(fs, dir)) {
|
|
286
|
+
if (e.isSymlink) links.push(`${r}/${e.rel}`);
|
|
287
|
+
else if (e.isFile && /\.pyc$/.test(e.rel)) pyc.push(`${r}/${e.rel}`);
|
|
288
|
+
else if (e.isDir && /(^|\/)__pycache__$/.test(e.rel)) pyc.push(`${r}/${e.rel}/`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const offenders = [...pyc, ...links];
|
|
292
|
+
if (offenders.length === 0) {
|
|
293
|
+
return { ok: true, detail: "no tracked .pyc/__pycache__ or symlinks in framework paths" };
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
ok: false,
|
|
297
|
+
detail: `found ${pyc.length} .pyc/__pycache__ and ${links.length} symlink(s): ${offenders.slice(0, 6).join(", ")}${offenders.length > 6 ? ` (+${offenders.length - 6})` : ""}`,
|
|
298
|
+
fix: `remove build artifacts: ${pyc.length ? "find scripts lib -name '*.pyc' -delete; find scripts lib -name __pycache__ -type d -prune -exec rm -rf {} +" : ""}${links.length ? " ; remove the listed symlink(s)" : ""}`,
|
|
299
|
+
// Only the .pyc/__pycache__ half is safe to auto-remove; symlinks may be
|
|
300
|
+
// intentional, so we only auto-fix when there are no symlink offenders.
|
|
301
|
+
fixable: links.length === 0 && pyc.length > 0,
|
|
302
|
+
applyFix(root, d) {
|
|
303
|
+
const { fs } = resolveDeps(d);
|
|
304
|
+
let allOk = true;
|
|
305
|
+
for (const rel of pyc) {
|
|
306
|
+
const p = join(root, rel);
|
|
307
|
+
try { fs.rmSync(p, { recursive: true, force: true }); }
|
|
308
|
+
catch { allOk = false; }
|
|
309
|
+
}
|
|
310
|
+
return allOk;
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
{
|
|
317
|
+
id: "backup-configured",
|
|
318
|
+
severity: "medium",
|
|
319
|
+
title: "Off-machine backup is configured and enabled",
|
|
320
|
+
run(agentRoot, deps) {
|
|
321
|
+
const { fs } = resolveDeps(deps);
|
|
322
|
+
const cfg = safeRead(fs, join(agentRoot, ".maestro/backup-config.yaml"));
|
|
323
|
+
if (!cfg) {
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
detail: "no .maestro/backup-config.yaml — a dead Mac mini loses all audit/cost history (G6/G16)",
|
|
327
|
+
fix: "configure off-machine backup (scripts/maintenance/backup-to-cloud.sh) and set enabled: true",
|
|
328
|
+
fixable: false,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (!/enabled:\s*true/.test(cfg)) {
|
|
332
|
+
return {
|
|
333
|
+
ok: false,
|
|
334
|
+
detail: "backup config present but enabled: false",
|
|
335
|
+
fix: "set enabled: true in .maestro/backup-config.yaml and schedule the backup job",
|
|
336
|
+
fixable: false,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return { ok: true, detail: "off-machine backup configured (enabled: true)" };
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
{
|
|
344
|
+
id: "disclosure-policy-present",
|
|
345
|
+
severity: "high",
|
|
346
|
+
title: "AI-disclosure policy is present and enabled",
|
|
347
|
+
run(agentRoot, deps) {
|
|
348
|
+
const { fs } = resolveDeps(deps);
|
|
349
|
+
// EU AI Act Art. 50 (G10): the agent must not deny being an AI. The
|
|
350
|
+
// shipped control lives in config/validation-rules.yaml (ai_disclosure)
|
|
351
|
+
// and the disclosure_boundaries.py gate. Verify the policy exists & is on.
|
|
352
|
+
const rules = safeRead(fs, join(agentRoot, "config/validation-rules.yaml"));
|
|
353
|
+
if (!rules) {
|
|
354
|
+
return {
|
|
355
|
+
ok: false,
|
|
356
|
+
detail: "config/validation-rules.yaml missing — no AI-disclosure gate",
|
|
357
|
+
fix: "run `maestro upgrade` to install config/validation-rules.yaml",
|
|
358
|
+
fixable: false,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
// The ai_disclosure block must exist and not be disabled.
|
|
362
|
+
const block = rules.match(/ai_disclosure:[\s\S]*?(?=\n\w|\n*$)/);
|
|
363
|
+
if (!block) {
|
|
364
|
+
return {
|
|
365
|
+
ok: false,
|
|
366
|
+
detail: "validation-rules.yaml has no ai_disclosure block",
|
|
367
|
+
fix: "add an ai_disclosure block (enabled: true, severity: block) to config/validation-rules.yaml",
|
|
368
|
+
fixable: false,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (/enabled:\s*false/.test(block[0])) {
|
|
372
|
+
return {
|
|
373
|
+
ok: false,
|
|
374
|
+
detail: "ai_disclosure policy is enabled: false — agent may deny being an AI (Art. 50 risk)",
|
|
375
|
+
fix: "set ai_disclosure.enabled: true in config/validation-rules.yaml",
|
|
376
|
+
fixable: false,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return { ok: true, detail: "AI-disclosure policy present and enabled" };
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
{
|
|
384
|
+
id: "daemon-hooks-wired",
|
|
385
|
+
severity: "medium",
|
|
386
|
+
title: "Daemon audit/lifecycle hooks are wired",
|
|
387
|
+
run(agentRoot, deps) {
|
|
388
|
+
const { fs } = resolveDeps(deps);
|
|
389
|
+
const settings = safeRead(fs, join(agentRoot, ".claude/settings.json"));
|
|
390
|
+
if (!settings) {
|
|
391
|
+
return { ok: false, detail: ".claude/settings.json missing — no PostToolUse/Stop audit hooks", fix: "run `maestro upgrade`", fixable: false };
|
|
392
|
+
}
|
|
393
|
+
let cfg;
|
|
394
|
+
try { cfg = JSON.parse(settings); } catch {
|
|
395
|
+
return { ok: false, detail: ".claude/settings.json is not valid JSON", fix: "repair .claude/settings.json", fixable: false };
|
|
396
|
+
}
|
|
397
|
+
const hooks = cfg.hooks || {};
|
|
398
|
+
const post = JSON.stringify(hooks.PostToolUse || []);
|
|
399
|
+
const stop = JSON.stringify(hooks.Stop || []);
|
|
400
|
+
const missing = [];
|
|
401
|
+
if (!post.includes("post-action-log.sh")) missing.push("PostToolUse: post-action-log.sh");
|
|
402
|
+
if (!stop.includes("session-end-log.sh")) missing.push("Stop: session-end-log.sh");
|
|
403
|
+
// The referenced scripts must exist on disk, not just be named.
|
|
404
|
+
for (const s of ["post-action-log.sh", "session-end-log.sh"]) {
|
|
405
|
+
if (!exists(fs, join(agentRoot, "scripts/hooks", s))) missing.push(`scripts/hooks/${s} missing`);
|
|
406
|
+
}
|
|
407
|
+
if (missing.length === 0) {
|
|
408
|
+
return { ok: true, detail: "daemon audit hooks wired (PostToolUse + Stop) and present on disk" };
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
ok: false,
|
|
412
|
+
detail: `audit hooks incomplete — ${missing.join("; ")}`,
|
|
413
|
+
fix: "run `maestro upgrade` to restore the daemon audit hooks",
|
|
414
|
+
fixable: false,
|
|
415
|
+
};
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
];
|
|
419
|
+
|
|
420
|
+
// ---------------------------------------------------------------------------
|
|
421
|
+
// Suppressions — config/audit-suppressions.yaml: a list of
|
|
422
|
+
// { id, reason, until }
|
|
423
|
+
// A suppression silences exactly one check id, REQUIRES a non-empty reason, and
|
|
424
|
+
// (if `until` is present) is honoured only before that date. Fails safe: any
|
|
425
|
+
// malformed entry is dropped, never trusted. We parse with js-yaml when
|
|
426
|
+
// available and fall back to a tiny line parser so the engine has no hard dep
|
|
427
|
+
// at import time (matches lib/setup/completeness.mjs).
|
|
428
|
+
// ---------------------------------------------------------------------------
|
|
429
|
+
|
|
430
|
+
const SUPPRESSIONS_REL = "config/audit-suppressions.yaml";
|
|
431
|
+
|
|
432
|
+
async function getYaml() {
|
|
433
|
+
try {
|
|
434
|
+
const m = await import("js-yaml");
|
|
435
|
+
return m && m.default ? m.default : m;
|
|
436
|
+
} catch {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Load + validate suppressions. Returns a Map<id, {reason, until}> of ACTIVE
|
|
443
|
+
* (non-expired, well-formed, reasoned) suppressions. `now` is injectable for
|
|
444
|
+
* deterministic expiry tests.
|
|
445
|
+
*
|
|
446
|
+
* @param {string} agentRoot
|
|
447
|
+
* @param {{ fs?: object, now?: number, suppressions?: Array }} [deps]
|
|
448
|
+
*/
|
|
449
|
+
export async function loadSuppressions(agentRoot, deps = {}) {
|
|
450
|
+
const { fs } = resolveDeps(deps);
|
|
451
|
+
const now = typeof deps.now === "number" ? deps.now : Date.now();
|
|
452
|
+
let raw = deps.suppressions;
|
|
453
|
+
if (!Array.isArray(raw)) {
|
|
454
|
+
const body = safeRead(fs, join(agentRoot, SUPPRESSIONS_REL));
|
|
455
|
+
if (!body) return new Map();
|
|
456
|
+
const yaml = await getYaml();
|
|
457
|
+
if (yaml) {
|
|
458
|
+
try {
|
|
459
|
+
const doc = yaml.load(body);
|
|
460
|
+
raw = Array.isArray(doc) ? doc : (doc && Array.isArray(doc.suppressions) ? doc.suppressions : []);
|
|
461
|
+
} catch { raw = []; }
|
|
462
|
+
} else {
|
|
463
|
+
raw = parseSuppressionsFallback(body);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const active = new Map();
|
|
467
|
+
for (const entry of raw) {
|
|
468
|
+
if (!entry || typeof entry !== "object") continue;
|
|
469
|
+
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
470
|
+
const reason = typeof entry.reason === "string" ? entry.reason.trim() : "";
|
|
471
|
+
if (!id || !reason) continue; // a suppression with no reason is not honoured
|
|
472
|
+
if (entry.until != null) {
|
|
473
|
+
const ts = Date.parse(String(entry.until));
|
|
474
|
+
if (Number.isFinite(ts) && ts < now) continue; // expired
|
|
475
|
+
}
|
|
476
|
+
active.set(id, { reason, until: entry.until != null ? String(entry.until) : null });
|
|
477
|
+
}
|
|
478
|
+
return active;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/** Minimal YAML-list-of-maps parser for the no-js-yaml fallback. */
|
|
482
|
+
function parseSuppressionsFallback(body) {
|
|
483
|
+
const out = [];
|
|
484
|
+
let cur = null;
|
|
485
|
+
for (const line of body.split(/\r?\n/)) {
|
|
486
|
+
if (/^\s*#/.test(line) || line.trim() === "") continue;
|
|
487
|
+
const item = line.match(/^\s*-\s*(.*)$/);
|
|
488
|
+
if (item) {
|
|
489
|
+
if (cur) out.push(cur);
|
|
490
|
+
cur = {};
|
|
491
|
+
const inline = item[1].match(/^(\w+):\s*(.*)$/);
|
|
492
|
+
if (inline) cur[inline[1]] = unquote(inline[2]);
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const kv = line.match(/^\s+(\w+):\s*(.*)$/);
|
|
496
|
+
if (kv && cur) cur[kv[1]] = unquote(kv[2]);
|
|
497
|
+
}
|
|
498
|
+
if (cur) out.push(cur);
|
|
499
|
+
return out;
|
|
500
|
+
}
|
|
501
|
+
function unquote(s) {
|
|
502
|
+
const t = s.trim();
|
|
503
|
+
if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) {
|
|
504
|
+
return t.slice(1, -1);
|
|
505
|
+
}
|
|
506
|
+
return t;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// ---------------------------------------------------------------------------
|
|
510
|
+
// Runner
|
|
511
|
+
// ---------------------------------------------------------------------------
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Run every check against `agentRoot`. A failed check fails the run UNLESS it
|
|
515
|
+
* carries an active, reasoned suppression — in which case it's reported under
|
|
516
|
+
* `suppressed` and does not affect `ok`. `info`-severity failures never flip
|
|
517
|
+
* `ok` on their own (they're advisory).
|
|
518
|
+
*
|
|
519
|
+
* @param {string} agentRoot
|
|
520
|
+
* @param {{ fs?: object, env?: object, now?: number, suppressions?: Array, checks?: Array }} [opts]
|
|
521
|
+
* @returns {Promise<{ ok, results, suppressed, summary }>}
|
|
522
|
+
*/
|
|
523
|
+
export async function runAudit(agentRoot, opts = {}) {
|
|
524
|
+
const checks = opts.checks || CHECKS;
|
|
525
|
+
const deps = { fs: opts.fs, env: opts.env };
|
|
526
|
+
const suppressionMap = await loadSuppressions(agentRoot, { fs: opts.fs, now: opts.now, suppressions: opts.suppressions });
|
|
527
|
+
|
|
528
|
+
const results = [];
|
|
529
|
+
const suppressed = [];
|
|
530
|
+
for (const check of checks) {
|
|
531
|
+
let outcome;
|
|
532
|
+
try {
|
|
533
|
+
outcome = check.run(agentRoot, deps);
|
|
534
|
+
} catch (err) {
|
|
535
|
+
// A check that throws is a posture failure, not a crash — record it.
|
|
536
|
+
outcome = { ok: false, detail: `check errored: ${err && err.message ? err.message : String(err)}` };
|
|
537
|
+
}
|
|
538
|
+
const sup = suppressionMap.get(check.id);
|
|
539
|
+
const row = {
|
|
540
|
+
id: check.id,
|
|
541
|
+
severity: check.severity,
|
|
542
|
+
title: check.title,
|
|
543
|
+
ok: !!outcome.ok,
|
|
544
|
+
detail: outcome.detail || "",
|
|
545
|
+
fix: outcome.ok ? undefined : outcome.fix,
|
|
546
|
+
fixable: outcome.ok ? false : !!outcome.fixable,
|
|
547
|
+
};
|
|
548
|
+
if (!row.ok && sup) {
|
|
549
|
+
row.suppressed = true;
|
|
550
|
+
row.suppressionReason = sup.reason;
|
|
551
|
+
if (sup.until) row.suppressionUntil = sup.until;
|
|
552
|
+
suppressed.push(row);
|
|
553
|
+
}
|
|
554
|
+
results.push(row);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// A run is OK iff every NON-suppressed, non-info failure is absent.
|
|
558
|
+
const ok = results.every((r) => r.ok || r.suppressed || r.severity === "info");
|
|
559
|
+
|
|
560
|
+
const summary = summarise(results);
|
|
561
|
+
return { ok, results, suppressed, summary };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** Aggregate counts for the CLI/org-server consumer. */
|
|
565
|
+
function summarise(results) {
|
|
566
|
+
const counts = { total: results.length, passed: 0, failed: 0, suppressed: 0 };
|
|
567
|
+
const bySeverity = Object.create(null);
|
|
568
|
+
for (const r of results) {
|
|
569
|
+
if (r.ok) counts.passed++;
|
|
570
|
+
else if (r.suppressed) counts.suppressed++;
|
|
571
|
+
else counts.failed++;
|
|
572
|
+
if (!r.ok && !r.suppressed) bySeverity[r.severity] = (bySeverity[r.severity] || 0) + 1;
|
|
573
|
+
}
|
|
574
|
+
// Highest unsuppressed failing severity, for a single at-a-glance signal.
|
|
575
|
+
let worst = null;
|
|
576
|
+
for (const r of results) {
|
|
577
|
+
if (r.ok || r.suppressed || r.severity === "info") continue;
|
|
578
|
+
if (worst == null || SEVERITY_RANK[r.severity] > SEVERITY_RANK[worst]) worst = r.severity;
|
|
579
|
+
}
|
|
580
|
+
return { ...counts, bySeverity, worstSeverity: worst };
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Apply the safe fixes for every fixable, currently-failing, non-suppressed
|
|
585
|
+
* check, then return a re-run. Only checks that declare `applyFix` and are
|
|
586
|
+
* marked `fixable` are touched; everything else is left for the operator.
|
|
587
|
+
*
|
|
588
|
+
* @returns {Promise<{ fixed: string[], audit: object }>}
|
|
589
|
+
*/
|
|
590
|
+
export async function applyFixes(agentRoot, opts = {}) {
|
|
591
|
+
const deps = { fs: opts.fs, env: opts.env };
|
|
592
|
+
const fixed = [];
|
|
593
|
+
for (const check of (opts.checks || CHECKS)) {
|
|
594
|
+
let outcome;
|
|
595
|
+
try { outcome = check.run(agentRoot, deps); } catch { continue; }
|
|
596
|
+
if (outcome.ok || !outcome.fixable || typeof outcome.applyFix !== "function") continue;
|
|
597
|
+
try {
|
|
598
|
+
if (outcome.applyFix(agentRoot, deps)) fixed.push(check.id);
|
|
599
|
+
} catch { /* a fix that throws is just not applied */ }
|
|
600
|
+
}
|
|
601
|
+
const audit = await runAudit(agentRoot, opts);
|
|
602
|
+
return { fixed, audit };
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ---------------------------------------------------------------------------
|
|
606
|
+
// Attestation — hash-locked per-agent conformance artifact.
|
|
607
|
+
// ---------------------------------------------------------------------------
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Read the agent's policy/archetype identity that the attestation binds to.
|
|
611
|
+
* Pulls the function/altitude (archetype) and a digest of the governing
|
|
612
|
+
* policies from disk. Pure over injected fs; missing fields degrade to "" so
|
|
613
|
+
* the hash is still well-defined (and changes when they appear).
|
|
614
|
+
*/
|
|
615
|
+
function readPolicyIdentity(agentRoot, deps) {
|
|
616
|
+
const { fs } = resolveDeps(deps);
|
|
617
|
+
const identity = { function: "", altitude: "", policyDigest: "" };
|
|
618
|
+
const agentJson = safeRead(fs, join(agentRoot, "config/agent.json"));
|
|
619
|
+
if (agentJson) {
|
|
620
|
+
try {
|
|
621
|
+
const a = JSON.parse(agentJson);
|
|
622
|
+
identity.function = String(a.function || "");
|
|
623
|
+
identity.altitude = String(a.altitude || "");
|
|
624
|
+
} catch { /* leave blank — blank is a stable, hashable state */ }
|
|
625
|
+
}
|
|
626
|
+
// Policy files that define the agent's governance posture. Their combined
|
|
627
|
+
// content digest is part of the attestation: change a policy → change the hash.
|
|
628
|
+
const POLICY_FILES = [
|
|
629
|
+
"policies/action-classification.yaml",
|
|
630
|
+
"policies/information-barriers.yaml",
|
|
631
|
+
"policies/prompt-injection-defence.yaml",
|
|
632
|
+
"config/validation-rules.yaml",
|
|
633
|
+
];
|
|
634
|
+
const h = createHash("sha256");
|
|
635
|
+
for (const rel of POLICY_FILES) {
|
|
636
|
+
const body = safeRead(fs, join(agentRoot, rel));
|
|
637
|
+
h.update(rel + "\0" + (body == null ? "" : body) + "\0");
|
|
638
|
+
}
|
|
639
|
+
identity.policyDigest = h.digest("hex");
|
|
640
|
+
return identity;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Produce a deterministic, hash-locked attestation over the policy/archetype
|
|
645
|
+
* identity + the audit result. The org server aggregates these into a per-agent
|
|
646
|
+
* conformance artifact and flags drift when the hash changes.
|
|
647
|
+
*
|
|
648
|
+
* The hash covers a canonical projection of the result (id → ok/severity/
|
|
649
|
+
* suppressed) plus the policy identity — NOT the human-readable `detail` lines,
|
|
650
|
+
* so cosmetic wording changes don't churn the hash, but any posture or policy
|
|
651
|
+
* change does. `generatedAt` is carried for humans but excluded from the hash
|
|
652
|
+
* so the same posture re-attested tomorrow yields the same lock.
|
|
653
|
+
*
|
|
654
|
+
* @param {object} audit result from runAudit
|
|
655
|
+
* @param {string} agentRoot
|
|
656
|
+
* @param {{ fs?: object, now?: number, agentId?: string }} [deps]
|
|
657
|
+
* @returns {{ schema, agentId, identity, posture, ok, hash, generatedAt }}
|
|
658
|
+
*/
|
|
659
|
+
export function buildAttestation(audit, agentRoot, deps = {}) {
|
|
660
|
+
const identity = readPolicyIdentity(agentRoot, deps);
|
|
661
|
+
// Canonical posture projection — sorted by id, stable field order.
|
|
662
|
+
const posture = audit.results
|
|
663
|
+
.map((r) => ({ id: r.id, severity: r.severity, ok: r.ok, suppressed: !!r.suppressed }))
|
|
664
|
+
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
665
|
+
|
|
666
|
+
const canonical = JSON.stringify({
|
|
667
|
+
schema: "maestro.audit.attestation.v1",
|
|
668
|
+
identity,
|
|
669
|
+
ok: !!audit.ok,
|
|
670
|
+
posture,
|
|
671
|
+
});
|
|
672
|
+
const hash = createHash("sha256").update(canonical).digest("hex");
|
|
673
|
+
|
|
674
|
+
const now = typeof deps.now === "number" ? deps.now : Date.now();
|
|
675
|
+
return {
|
|
676
|
+
schema: "maestro.audit.attestation.v1",
|
|
677
|
+
agentId: deps.agentId || identity.function || "",
|
|
678
|
+
identity,
|
|
679
|
+
posture,
|
|
680
|
+
ok: !!audit.ok,
|
|
681
|
+
hash,
|
|
682
|
+
generatedAt: new Date(now).toISOString(),
|
|
683
|
+
};
|
|
684
|
+
}
|