wendkeep 0.87.0 → 0.89.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.en.md +3 -2
  3. package/README.md +3 -2
  4. package/bin/wendkeep.mjs +1 -0
  5. package/docs/en/commands/ecosystem-bridges.md +172 -0
  6. package/docs/en/commands/observer-security.md +154 -0
  7. package/docs/en/commands/observer.md +30 -12
  8. package/docs/en/commands/verify.md +6 -0
  9. package/docs/pt-BR/commands/ecosystem-bridges.md +169 -0
  10. package/docs/pt-BR/commands/observer-security.md +154 -0
  11. package/docs/pt-BR/commands/observer.md +30 -12
  12. package/docs/pt-BR/commands/verify.md +6 -0
  13. package/hooks/observer-publish.mjs +3 -1
  14. package/package.json +2 -1
  15. package/packages/cli/src/index.mjs +10 -1
  16. package/packages/harness/src/sensors-core.mjs +49 -3
  17. package/packages/integrations/src/bridge-config.mjs +139 -0
  18. package/packages/integrations/src/bridge-contract.mjs +316 -0
  19. package/packages/integrations/src/bridge-diagnostics.mjs +45 -0
  20. package/packages/integrations/src/canonical-bridge-authority.mjs +32 -0
  21. package/packages/integrations/src/capabilities.mjs +34 -0
  22. package/packages/integrations/src/ecosystem-bridge.mjs +82 -0
  23. package/packages/integrations/src/index.mjs +6 -0
  24. package/packages/integrations/src/spec-kit-adapter.mjs +259 -0
  25. package/packages/integrations/src/superpowers-adapter.mjs +269 -0
  26. package/packages/mcp/src/executor.mjs +35 -2
  27. package/packages/observer/package.json +16 -0
  28. package/packages/observer/src/audit.mjs +1 -0
  29. package/packages/observer/src/authz.mjs +38 -0
  30. package/packages/observer/src/encryption.mjs +75 -0
  31. package/packages/observer/src/index.mjs +7 -0
  32. package/packages/observer/src/policy.mjs +305 -0
  33. package/packages/observer/src/purge.mjs +100 -0
  34. package/packages/observer/src/redaction.mjs +54 -0
  35. package/packages/observer/src/retention.mjs +39 -0
  36. package/packages/observer/src/token-registry.mjs +122 -0
  37. package/schema/ecosystem-bridge-artifact-manifest-v1.schema.json +30 -0
  38. package/schema/ecosystem-bridge-v1.schema.json +65 -0
  39. package/schema/observer/006-observer-security.sql +64 -0
  40. package/schema/observer-policy-v1.schema.json +63 -0
  41. package/schema/sync-event-v1.schema.json +10 -0
  42. package/schema/wendkeep.evidence-envelope-v2.schema.json +39 -0
  43. package/schema/wendkeep.sensors.schema.json +14 -0
  44. package/src/doctor.mjs +6 -1
  45. package/src/ecosystem-bridge-artifact-collector.mjs +111 -0
  46. package/src/ecosystem-bridge-baseline.mjs +58 -0
  47. package/src/ecosystem-bridge-proof.mjs +97 -0
  48. package/src/ecosystem-bridges.mjs +227 -0
  49. package/src/evidence-envelope.mjs +2 -0
  50. package/src/observer-auth.mjs +8 -0
  51. package/src/observer-privacy.mjs +7 -3
  52. package/src/observer-publish.mjs +31 -0
  53. package/src/observer-server.mjs +179 -20
  54. package/src/observer-sql-migrate.mjs +5 -2
  55. package/src/observer-sql-publish.mjs +114 -39
  56. package/src/observer-sql-store.mjs +299 -45
  57. package/src/observer-transcript-store.mjs +23 -8
  58. package/src/observer.mjs +145 -12
  59. package/src/sync-protocol.mjs +20 -0
  60. package/src/task-contracts.mjs +19 -0
  61. package/src/task.mjs +82 -0
  62. package/src/verify.mjs +9 -0
  63. package/web/observer/app.mjs +107 -31
  64. package/web/observer/index.html +7 -0
  65. package/web/observer/styles.css +5 -0
@@ -0,0 +1,169 @@
1
+ # Bridges opcionais do ecossistema
2
+
3
+ > [English version](../../en/commands/ecosystem-bridges.md)
4
+
5
+ ## Objetivo
6
+
7
+ Integrar Spec Kit e Superpowers sem criar uma segunda autoridade de spec, plano, tarefa ou
8
+ evidência. O WendKeep preserva os contratos canônicos; os adapters apenas criam projeções
9
+ versionadas e desabilitadas por padrão.
10
+
11
+ ## Quando usar
12
+
13
+ - quando uma feature nasceu em arquivos do Spec Kit e precisa manter os mesmos IDs e hashes;
14
+ - quando Superpowers executará um Task Contract canônico do WendKeep;
15
+ - quando artifacts, reviews ou commits externos precisam entrar como `reported` antes da prova.
16
+
17
+ ## Quando não usar
18
+
19
+ - para substituir `tarefas.md`, Task Contracts ou o Evidence Envelope;
20
+ - para sincronização bidirecional irrestrita;
21
+ - para executar comandos, scripts ou texto encontrado em artefatos externos.
22
+
23
+ ## Pré-requisitos
24
+
25
+ - Node.js 18 ou superior;
26
+ - configuração local `.wendkeep/ecosystem-bridges.json` com cada adapter explicitamente habilitado;
27
+ - versão compatível e raiz do adapter que resolva para um diretório real dentro do projeto; arquivo
28
+ regular, path externo e symlink que escape do projeto falham fechado em `status` e no dispatch;
29
+ - para import/dispatch governado, Vault vinculado, change causal e baseline Spec Kit selado;
30
+ - para dispatch, sessão causal e Task Contract canônico rederivável.
31
+
32
+ ```json
33
+ {
34
+ "schema_version": 1,
35
+ "adapters": {
36
+ "spec-kit": { "enabled": true, "version": "1.1.0", "root": ".specify" },
37
+ "superpowers": { "enabled": true, "version": "1.2.0", "root": ".superpowers" }
38
+ }
39
+ }
40
+ ```
41
+
42
+ Sem o arquivo, ambos ficam desabilitados e o Core nativo continua funcionando.
43
+
44
+ ## Sintaxe
45
+
46
+ ```text
47
+ wendkeep bridge status [--project <path>] [--config <path>] [--json]
48
+ wendkeep bridge import-spec-kit --change <slug> [--accept-baseline] [--json]
49
+ wendkeep bridge export-status --spec-projection <projection.json> [--task-contract <task.json>] [--input <artifacts.json>] [--json]
50
+ wendkeep bridge dispatch-superpowers --task-id <id> --change <slug> [--task-contract <task.json>] [--session <id>] --spec-projection <projection.json> [--json]
51
+ wendkeep bridge verify-artifacts --input <artifacts.json> --proofs <proofs.json> --change <slug> [--session <id>] [--json]
52
+ ```
53
+
54
+ `import-spec-kit` lê Markdown sob `memory/` e `specs/`, classifica constitution/spec/plan/task,
55
+ preserva IDs e SHA-256, cria mappings explícitos `story|requirement → capability → change → task`
56
+ e nunca escreve na origem. IDs repetidos em arquivos diferentes bloqueiam a projeção.
57
+ O primeiro import exige `--accept-baseline` e ancora a projeção verde na change do Vault; imports
58
+ seguintes rederivam a origem e comparam path, kind, hash e mapping contra esse baseline imutável.
59
+ `dispatch-superpowers` contém somente o contexto estrutural
60
+ mínimo derivado do Task Contract; transcript, conteúdo privado e ownership externo não entram.
61
+ O dispatch rederiva o contrato e o active context do Vault/checkout; um JSON submetido é apenas
62
+ uma cópia para comparação e nunca valida o próprio `binding`. Com Spec Kit ativo, baseline e
63
+ `--spec-projection` são obrigatórios e a origem é reimportada antes do dispatch.
64
+ O contrato `spec-projection` pertence exclusivamente ao adapter `spec-kit`: versão incompatível,
65
+ kind fora do schema ou projeção re-selada por outro adapter é rejeitada antes de produzir `spec_refs`.
66
+ Uma decisão `ok: false` exige ao menos um diagnóstico bloqueante, e `ok: true` não pode coexistir
67
+ com diagnóstico bloqueante; qualquer incoerência bloqueia o dispatch sem expor referências.
68
+ `export-status` recalcula e valida `projection_id`, devolve apenas uma projeção `reported` e não
69
+ grava nos arquivos do Spec Kit.
70
+
71
+ ## Opções e códigos de saída
72
+
73
+ | Opção | Efeito |
74
+ |---|---|
75
+ | `--project <path>` | Seleciona a raiz do consumidor. |
76
+ | `--config <path>` | Substitui `.wendkeep/ecosystem-bridges.json`. |
77
+ | `--change <slug>` | Seleciona a change que guarda baseline e Evidence Envelope canônicos. |
78
+ | `--accept-baseline` | Ancora somente o primeiro baseline Spec Kit verde; não sobrescreve drift. |
79
+ | `--task-id <id>` | Seleciona a tarefa no contexto causal e rederiva o contrato canônico. |
80
+ | `--task-contract <path>` | Cópia opcional que deve coincidir com o contrato rederivado. |
81
+ | `--spec-projection <path>` | Liga referências Spec Kit ao dispatch sem copiar o conteúdo. |
82
+ | `--input` / `--proofs` | Classifica artifacts externos e suas provas Git/CI/Envelope. |
83
+ | `--json` | Emite o contrato tipado em uma linha JSON. |
84
+
85
+ - `0`: operação válida; adapters opcionais desabilitados também são saudáveis;
86
+ - `1`: adapter habilitado bloqueado, drift, incompatibilidade ou prova ausente;
87
+ - `2`: argumento, configuração ou arquivo de entrada inválido.
88
+
89
+ ## Exemplos
90
+
91
+ Fluxo pequeno, sem adapters:
92
+
93
+ ```powershell
94
+ node ./bin/wendkeep.mjs bridge status --json
95
+ ```
96
+
97
+ Fluxo médio, Spec Kit somente leitura:
98
+
99
+ ```powershell
100
+ node ./bin/wendkeep.mjs bridge import-spec-kit --change ecosystem-bridges --accept-baseline --json > spec-projection.json
101
+ node ./bin/wendkeep.mjs bridge dispatch-superpowers --task-id 3.1 --change ecosystem-bridges --session "$env:CODEX_THREAD_ID" --spec-projection spec-projection.json --json
102
+ node ./bin/wendkeep.mjs bridge export-status --spec-projection spec-projection.json --task-contract task-contract.json --json
103
+ ```
104
+
105
+ Fluxo grande, ingestão de relato e prova externa:
106
+
107
+ ```powershell
108
+ node ./bin/wendkeep.mjs verify --change ecosystem-bridges
109
+ node ./bin/wendkeep.mjs bridge verify-artifacts --input artifacts.json --proofs ci-proofs.json --change ecosystem-bridges --json
110
+ ```
111
+
112
+ Antes de `verify`, versione no índice Git o artefato e `.wendkeep/bridge-artifacts.json`. O manifest
113
+ v1 liga cada item a `source`, `external_id`, `kind`, `path`, `sensor_id` e `task_id`. O sensor
114
+ correspondente em `wendkeep.sensors.json` declara `artifact_results` v1 com `external_id`, `path` e
115
+ `algorithm: sha256`; o runner calcula o digest dos bytes, inclusive binários, somente depois de uma
116
+ execução verde. O resultado explícito não contém conteúdo, transcript nem output tail do artefato.
117
+ O collector consulta primeiro o índice e então exige a mesma cópia presente na worktree; apagar ou
118
+ alterar somente a cópia de trabalho falha fechado, enquanto a ausência simultânea no índice e na
119
+ worktree continua significando que o bridge opcional não declarou artifacts.
120
+
121
+ Cada referência em `ci-proofs.json` usa apenas
122
+ `{"type":"evidence-envelope","external_id":"review-1"}`. Path, task, sensor, digests e blobs Git
123
+ são rederivados do manifest e do Envelope canônicos; `state`, SHA ou autoridade autodeclarados são
124
+ ignorados para promoção. `artifacts.json` continua sendo somente o relato externo a comparar.
125
+
126
+ O artifact começa como `reported`. JSON externo que apenas declare `state: verified` continua
127
+ `reported`. A promoção para `verified` exige, em conjunto: arquivo dentro do projeto e igual ao
128
+ blob do índice Git, sensor CI verde com `artifact_results.digest` explícito, Evidence Envelope v2
129
+ canônico e bound ao checkout, e entrada correspondente em `external_artifacts` do Envelope.
130
+ `output_sha256` não prova artefatos.
131
+ O resultado expõe uma proof selada ligada ao `evidence_envelope_id`, sem copiar transcript ou Vault.
132
+
133
+ ## Resultado esperado
134
+
135
+ - Spec Kit permanece uma fonte externa somente leitura;
136
+ - plano, tarefa e evidência canônicos continuam pertencendo ao WendKeep;
137
+ - Superpowers recebe um dispatch mínimo sem poder reescrever o escopo;
138
+ - criação/reuso e finalização de worktree permanecem nos comandos `wendkeep worktree` derivados no dispatch;
139
+ - cleanup pós-merge usa `wendkeep worktree finish <slug> --pr <número-ou-url>`;
140
+ - drift e ownership concorrente bloqueiam antes da execução;
141
+ - remover ou desabilitar um adapter não degrada o Core.
142
+
143
+ ## Erros comuns e diagnóstico
144
+
145
+ | Código | Diagnóstico |
146
+ |---|---|
147
+ | `BRIDGE_ADAPTER_DISABLED` | Estado opcional normal; habilite explicitamente se necessário. |
148
+ | `BRIDGE_ADAPTER_MISSING` | Adapter habilitado, mas sua raiz não existe. |
149
+ | `BRIDGE_VERSION_INCOMPATIBLE` | Versão fora do compatibility range publicado. |
150
+ | `BRIDGE_OWNERSHIP_CONFLICT` | Ferramenta externa tentou possuir plano/tarefa/evidência. |
151
+ | `BRIDGE_SOURCE_DRIFT` | Hash mudou, plano ficou obsoleto ou uma referência apareceu/desapareceu. |
152
+ | `BRIDGE_SOURCE_ID_DUPLICATE` | O mesmo story/requirement ID apareceu em arquivos diferentes. |
153
+ | `BRIDGE_BASELINE_MISSING` | Spec Kit ativo sem baseline canônico ou projeção obrigatória. |
154
+ | `BRIDGE_BASELINE_STALE` | Origem/projeção divergiu do baseline selado no Vault. |
155
+ | `BRIDGE_PROJECTION_INVALID` | Conteúdo e `projection_id` não coincidem ou o schema está incompleto. |
156
+ | `BRIDGE_SCHEMA_INVALID` | Envelope runtime não cumpre o contrato publicado. |
157
+ | `BRIDGE_ARTIFACT_MANIFEST_UNTRACKED` | Manifest bridge ausente em apenas um lado ou diferente entre índice e worktree. |
158
+ | `BRIDGE_ARTIFACT_FORGED` | Path/bytes do artefato não correspondem ao arquivo versionado. |
159
+ | `BRIDGE_ARTIFACT_RESULT_MISSING` | Sensor verde não produziu o digest explícito ligado ao manifest. |
160
+ | `BRIDGE_PROOF_MISSING` | Relato externo ainda não possui prova independente vinculada. |
161
+ | `BRIDGE_PROOF_UNVERIFIED` | Prova autodeclarada foi mantida como `reported`. |
162
+
163
+ `wendkeep doctor` mostra a seção `[bridges]` sem fazer import, dispatch ou escrita.
164
+
165
+ ## Próximos passos
166
+
167
+ Revise a projeção antes do dispatch, mantenha os arquivos gerados fora do controle canônico e
168
+ valide artifacts pelo CI ou Evidence Envelope. Consulte também [Changes e verificação](changes-and-verification.md)
169
+ e [Worktrees gerenciadas](worktrees.md).
@@ -0,0 +1,154 @@
1
+ # Segurança do Observer
2
+
3
+ **PT-BR** · [English](../../en/commands/observer-security.md)
4
+
5
+ ## Objetivo
6
+
7
+ O Observer é um read model local ou de equipe, nunca uma nova autoridade sobre Vault, spec, memória
8
+ ou sync. O modelo de ameaça considera host remoto comprometido, token roubado, operador curioso,
9
+ banco/outbox copiados, payload adversarial e purge interrompido. Host/Origin continuam validados;
10
+ mutações e leituras sensíveis falham fechadas, inclusive no loopback.
11
+
12
+ | Classe | Padrão | Risco principal |
13
+ |---|---|---|
14
+ | documento | `metadata` | memória/decisões integrais |
15
+ | transcript | `metadata` | conversa e ferramentas |
16
+ | prompt/resposta | `redacted` | PII e segredos |
17
+ | uso | `aggregate` | custo e identidade operacional |
18
+ | audit/receipt | metadados mínimos | apagar a própria prova |
19
+
20
+ A policy v1 restringe por classe, `project_id`, glob de path e `entity_type`. Regras mais tardias
21
+ vencem apenas no projeto correspondente. Redaction cobre Bearer, credenciais em URL/connection
22
+ string, access keys, e-mail, telefone e regexes configuráveis seguras. O schema é
23
+ `schema/observer-policy-v1.schema.json`.
24
+ Em `transcript_capture: messages`, arrays, JSONL e o envelope canônico `{messages:[...]}` preservam
25
+ somente mensagens `user|assistant|system` com `role`/`content` string após redaction; campos extras,
26
+ tools e entradas malformadas são descartados ou falham fechados.
27
+ A policy explícita é a única autoridade de captura do publisher; `WENDKEEP_OBSERVER_CAPTURE_LEVEL`
28
+ é apenas compatibilidade traduzida para policy quando nenhum arquivo de policy foi fornecido e
29
+ nunca eleva nem suprime `none|metadata|messages|full` ou documentos `selected` explícitos.
30
+ Nos upserts de documento/transcript, `content_hash` sempre representa o conteúdo final após captura
31
+ e redaction; captura metadata/selected usa o SHA-256 do conteúdo vazio. Exclusões de documento
32
+ continuam efetivas mesmo com captura `none`, preservam path/revision/operação e nunca transportam
33
+ conteúdo ou hash obsoleto.
34
+ A redaction nunca reescreve campos validados de identidade estrutural, como IDs de projeto/evento/
35
+ entidade, paths lógicos, revisions ou operações. A privacidade do path é aplicada de modo fail-closed
36
+ pelas regras de captura por projeto/path, não pela renomeação da chave de storage por uma regra de
37
+ redaction de conteúdo.
38
+ O contrato estrutural por evento também preserva aliases snake/camel aceitos, chaves de documento/
39
+ sessão/agente/call/transcript/rollup, timestamps, roles, status, coverage, dimensões de modelo/preço,
40
+ workflow e proveniência de source. `title`, `summary`, `agent_name`, conteúdo, prompt/resposta e
41
+ metadata continuam como campos de display/conteúdo sujeitos a redaction.
42
+ Na publicação incremental, timestamps de turn ausentes ou vazios herdam o instante canônico do
43
+ lote, epoch numérico em milissegundos é normalizado para ISO 8601 e valor não vazio inválido falha
44
+ fechado antes da policy/store; evento e payload usam o mesmo instante.
45
+
46
+ ## Quando usar
47
+
48
+ Use ao habilitar o Observer para dados reais, cadastrar ou revogar credenciais, restringir captura,
49
+ proteger SQLite/outbox, definir retenção ou eliminar dados com prova verificável.
50
+
51
+ ## Quando não usar
52
+
53
+ Não use como KMS/secret manager corporativo, para publicar Vault/runtime, para substituir a
54
+ autoridade local ou para apagar manualmente tabelas e índices. Captura `full` continua opt-in e
55
+ sujeita à policy/redaction.
56
+
57
+ ## Pré-requisitos
58
+
59
+ Use Node.js 22.13+, mantenha o bind no loopback e injete tokens/chaves somente por variáveis de
60
+ ambiente. O token de bootstrap é registrado somente pelo hash, exige projetos explícitos e
61
+ expiração finita; não é um admin wildcard fora do registry. Para Docker, defina também
62
+ `WENDKEEP_OBSERVER_BOOTSTRAP_PROJECTS`, `WENDKEEP_OBSERVER_BOOTSTRAP_EXPIRES_AT` e uma chave de
63
+ 32 bytes em hex/base64 em `WENDKEEP_OBSERVER_ENCRYPTION_KEY`. O operador guarda a chave e receipts externos.
64
+
65
+ ## Sintaxe
66
+
67
+ ```bash
68
+ npx wendkeep observer serve --token <token> --bootstrap-projects <p1,p2> --bootstrap-expires-at <ISO> [--bootstrap-token-id <id>] [--require-loopback-auth] [--require-encryption]
69
+ npx wendkeep observer security token create --project-id <projeto> --role <role> --scopes <scopes> --token-env <env> --expires-at <ISO>
70
+ npx wendkeep observer security token rotate --project-id <projeto> --token-id <id> --token-env <env> --expires-at <ISO> [--new-token-id <id>]
71
+ npx wendkeep observer security token revoke --project-id <projeto> --token-id <id>
72
+ npx wendkeep observer security policy set --project-id <projeto> --file <policy.json>
73
+ npx wendkeep observer security policy show --project-id <projeto>
74
+ npx wendkeep observer security purge --project-id <projeto> --before <ISO> --classes <classes> [--dry-run]
75
+ npx wendkeep observer security retention run --project-id <projeto> [--dry-run] [--operation-id <id>]
76
+ ```
77
+
78
+ ## Opções e códigos de saída
79
+
80
+ - `viewer` lê metadata/agregados; `auditor` pode receber scopes sensíveis; `publisher` ingere;
81
+ `admin` administra policy, purge e recovery. Role, scope e projeto precisam autorizar juntos.
82
+ - Tokens são persistidos somente como SHA-256; expiração, rotação e revogação valem sem restart.
83
+ - Após rotacionar o bootstrap, atualize token e token ID no ambiente; reiniciar nunca reativa a
84
+ credencial antiga revogada ou expirada.
85
+ - `--token-env` nomeia a variável com o segredo; o comando nunca imprime o valor.
86
+ - `--require-loopback-auth` protege toda a API; reads sensíveis exigem token mesmo sem a flag.
87
+ - `--require-encryption` falha se a chave externa estiver ausente ou inválida.
88
+ - `WENDKEEP_OBSERVER_REQUIRE_ENCRYPTION=1` aplica a mesma falha fechada a `status`, `security`,
89
+ `register`, `publish` e `reconcile`; com chave configurada, todo primeiro upgrade v5 usa apenas
90
+ `.bak.enc` + manifest antes de qualquer leitura/backfill.
91
+ - Exit `0` indica operação concluída; exit `1` indica configuração, autorização, policy, chave ou
92
+ operação inválida. O hook mantém exit `0` fail-open para o fluxo local, mas aborta antes de
93
+ persistir conteúdo inseguro.
94
+
95
+ O audit guarda capability, resultado, rota e horário, nunca Bearer, prompt, resposta ou payload.
96
+
97
+ ## Exemplos
98
+
99
+ Recovery offline explícito e auditado:
100
+
101
+ ```powershell
102
+ $env:OBSERVER_RECOVERY_TOKEN = '<segredo-forte-temporário>'
103
+ npx wendkeep observer security token create --data-dir C:\WendKeepObserver `
104
+ --project-id project-a --role admin --scopes '*' --token-env OBSERVER_RECOVERY_TOKEN `
105
+ --expires-at 2026-09-29T12:00:00Z --reason 'offline recovery' --json
106
+ npx wendkeep observer security token revoke --data-dir C:\WendKeepObserver `
107
+ --project-id project-a --token-id <id> --reason 'recovery complete' --json
108
+ ```
109
+
110
+ Sempre faça dry-run antes do purge. O runner de retenção é explícito/idempotente (CLI ou
111
+ `POST /v1/projects/:id/security/retention`), sem timer oculto:
112
+
113
+ ```powershell
114
+ npx wendkeep observer security purge --data-dir C:\WendKeepObserver `
115
+ --project-id project-a --before 2026-08-01T00:00:00Z `
116
+ --classes documents,calls,transcripts --dry-run --json
117
+ ```
118
+
119
+ ```powershell
120
+ npx wendkeep observer security retention run --data-dir C:\WendKeepObserver `
121
+ --project-id project-a --operation-id scheduled-2026-08-29 --dry-run --json
122
+ ```
123
+
124
+ ## Resultado esperado
125
+
126
+ TTL é independente para documentos, calls e transcripts. Contagens, remoção de projeções/FTS,
127
+ eventos e receipt usam a mesma transação; retry é idempotente e dado antigo tardio gera nova prova.
128
+
129
+ AES-256-GCM usa AAD por projeto/classe/registro/campo e `keyProvider` externo. O backfill v6 remove
130
+ plaintext e índices derivados antes de liberar leituras; chave errada falha sem revelar conteúdo.
131
+ A migration estrutural `006-observer-security.sql` cria backup, valida checksum, faz rollback e
132
+ permite retry. Em modo at-rest obrigatório, o backup é `.bak.enc`, tem manifest/key ID/permissão
133
+ restrita e restauração falha com chave errada; nenhum `.bak` plaintext permanece.
134
+
135
+ O hook aplica policy metadata/redacted por padrão; `WENDKEEP_OBSERVER_POLICY_FILE` seleciona policy
136
+ explícita. `WENDKEEP_OBSERVER_OUTBOX_KEY_ENV` nomeia a variável da chave da outbox e
137
+ `WENDKEEP_OBSERVER_OUTBOX_KEY_ID` identifica a chave. O Compose exige autenticação e criptografia.
138
+ O painel guarda Bearer somente em memória, exporta cópia sanitizada e expõe Segurança. MCP exige
139
+ scope para calls/busca integral. Sync leva apenas `policy_ref`, sem duplicar tokens ou autoridade.
140
+
141
+ ## Erros comuns e diagnóstico
142
+
143
+ - `observer_token_missing|expired|revoked`: crie/rotacione um token escopado ou faça recovery offline.
144
+ - `observer_project_forbidden|role_forbidden|scope_forbidden`: confira a interseção projeto/role/scope.
145
+ - `observer_encryption_key_unavailable|observer_decryption_failed`: confira key ID e material externo;
146
+ nunca enfraqueça o modo obrigatório.
147
+ - `observer_policy_invalid`: valide campos/captures e remova regex inválida ou explosiva.
148
+ - Falha de migration v6: preserve `.pre-006-*.bak.enc` e seu manifest, corrija a causa e repita.
149
+
150
+ ## Próximos passos
151
+
152
+ Leia [Observer local](observer.md), faça um dry-run de retenção, valide token revogado/expirado e
153
+ guarde o receipt fora do banco quando precisar de prova externa. Nunca publique banco, backup,
154
+ outbox, chave, token ou `/data`.
@@ -26,7 +26,9 @@ pelos hooks e pelo WendKeep local.
26
26
 
27
27
  Tenha Node.js 22.13 ou mais recente para executar o Observer SQL. O Keep Core e os demais comandos
28
28
  continuam compatíveis com Node.js 18 ou mais recente. Registre explicitamente cada projeto e defina
29
- `WENDKEEP_OBSERVER_TOKEN`; leituras no loopback permanecem abertas, mas toda mutação exige Bearer.
29
+ `WENDKEEP_OBSERVER_TOKEN`. Toda mutação e toda leitura de conteúdo sensível exigem Bearer, inclusive
30
+ no loopback; metadados e agregados podem permanecer abertos localmente quando
31
+ `--require-loopback-auth` não é usado. Veja [Segurança do Observer](observer-security.md).
30
32
 
31
33
  ## Sintaxe
32
34
 
@@ -36,7 +38,7 @@ npx wendkeep observer register --project <projeto> --vault <vault> --data-dir <d
36
38
  npx wendkeep observer publish --project <projeto> --vault <vault> --data-dir <diretório>
37
39
  npx wendkeep observer reconcile --project <projeto> --vault <vault> --data-dir <diretório> [--url http://127.0.0.1:8787]
38
40
  npx wendkeep observer memory import --project <projeto> --vault <vault> --url http://127.0.0.1:8787 --token <token> --json
39
- npx wendkeep observer serve --host 127.0.0.1 --port 8787 --data-dir <diretório> --token <token>
41
+ npx wendkeep observer serve --host 127.0.0.1 --port 8787 --data-dir <diretório> --token <token> --bootstrap-projects <p1,p2> --bootstrap-expires-at <ISO> [--require-loopback-auth] [--require-encryption]
40
42
  ```
41
43
 
42
44
  ## Opções e códigos de saída
@@ -46,7 +48,13 @@ npx wendkeep observer serve --host 127.0.0.1 --port 8787 --data-dir <diretório>
46
48
  - `--project` e `--vault` identificam o projeto nos comandos `register`, `publish`, `reconcile` e `memory import`.
47
49
  - `--host` aceita somente `127.0.0.1`, `localhost` ou `::1`; outros hosts são recusados antes do
48
50
  listen.
49
- - `--token` ou `WENDKEEP_OBSERVER_TOKEN` autentica mutações; `--allow-non-loopback` falha sem token.
51
+ - `--token` ou `WENDKEEP_OBSERVER_TOKEN` fornece o segredo de bootstrap hash-only; projetos
52
+ explícitos e expiração finita são obrigatórios, e toda mutação/leitura sensível passa pelo registry;
53
+ `--allow-non-loopback` falha sem token.
54
+ - `--require-loopback-auth` exige Bearer também para metadados e agregados locais e ativa a policy
55
+ segura do projeto na ingestão.
56
+ - `--require-encryption` exige `WENDKEEP_OBSERVER_ENCRYPTION_KEY` com 32 bytes em hex/base64; use
57
+ `WENDKEEP_OBSERVER_ENCRYPTION_KEY_ID` para identificar a chave externa.
50
58
  - `WENDKEEP_OBSERVER_CAPTURE_LEVEL` aceita `metadata` (padrão, sem mensagens), `messages` ou
51
59
  `full-transcript`. Caminhos locais absolutos nunca são publicados.
52
60
  - Exit `0` indica sucesso; exit `1` indica falha de configuração ou operação; o hook publisher
@@ -57,7 +65,10 @@ npx wendkeep observer serve --host 127.0.0.1 --port 8787 --data-dir <diretório>
57
65
  ```powershell
58
66
  npx wendkeep observer register --project C:\GitHub\WendKeep --vault C:\GitHub\WendKeep\.WendKeep-vault --data-dir C:\WendKeepObserver
59
67
  $env:WENDKEEP_OBSERVER_TOKEN = '<token-local-forte>'
60
- npx wendkeep observer serve --host 127.0.0.1 --port 8787 --data-dir C:\WendKeepObserver --token $env:WENDKEEP_OBSERVER_TOKEN
68
+ $env:WENDKEEP_OBSERVER_BOOTSTRAP_PROJECTS = 'project-a'
69
+ $env:WENDKEEP_OBSERVER_BOOTSTRAP_EXPIRES_AT = '2026-09-29T12:00:00Z'
70
+ $env:WENDKEEP_OBSERVER_ENCRYPTION_KEY = '<32-bytes-em-hex-ou-base64>'
71
+ npx wendkeep observer serve --host 127.0.0.1 --port 8787 --data-dir C:\WendKeepObserver --token $env:WENDKEEP_OBSERVER_TOKEN --require-loopback-auth
61
72
  $env:WENDKEEP_OBSERVER_URL = 'http://127.0.0.1:8787'
62
73
  ```
63
74
 
@@ -67,15 +78,19 @@ Para Docker local:
67
78
  docker compose -f docker/wendkeep-observer/compose.yaml up -d --build
68
79
  ```
69
80
 
81
+ O Compose exige token, allowlist/expiração do bootstrap e chave; inicia com autenticação integral
82
+ e criptografia obrigatória. A policy `encryption_required` recusa ingestão e outbox plaintext.
83
+
70
84
  ## Painel web local
71
85
 
72
86
  Com o servidor em execução, abra [http://127.0.0.1:8787/](http://127.0.0.1:8787/) no navegador.
73
- O painel é servido pelo mesmo processo e abre diretamente para consultas, sem formulário ou token. A porta fica
74
- presa ao loopback do computador; não coloque o endereço em uma interface de rede.
87
+ O painel é servido pelo mesmo processo. Informe o token no formulário local: ele fica somente na
88
+ memória da página, segue como Bearer nas consultas e é descartado ao recarregar. A porta fica presa
89
+ ao loopback do computador; não coloque o endereço em uma interface de rede.
75
90
 
76
91
  O painel mostra a lista multi-projeto, versão, saúde, sessão mais recente, change ativa, contagem
77
92
  de changes e data da última captura. Ao abrir um projeto, o workspace oferece Overview, Consumo,
78
- Sessões, Memória, Changes e Sincronização. A aba Consumo mostra custo total, tokens por categoria,
93
+ Sessões, Memória, Changes, Sincronização e Segurança. A aba Consumo mostra custo total, tokens por categoria,
79
94
  agentes principais, subagentes, provedores, modelos, tendência diária, cobertura histórica e
80
95
  chamadas conforme o nível de captura escolhido. Os estados de carregamento, vazio, servidor
81
96
  indisponível, conflito, modelo sem tarifa e dados desatualizados ficam visíveis, e a atualização
@@ -160,18 +175,21 @@ corte. As telas do Observer não concluem, arquivam, reparam ou promovem estado.
160
175
  - `POST /v1/projects/:project_id/ingest` — lote idempotente de documentos, sessões, agentes, rollups,
161
176
  chamadas e transcripts.
162
177
  - `GET /v1/projects/:project_id/memory/tree` — árvore e metadados dos documentos.
163
- - `GET /v1/projects/:project_id/memory/document?path=...` — conteúdo Markdown integral.
178
+ - `GET /v1/projects/:project_id/memory/document?path=...` — conteúdo Markdown integral; exige Bearer.
164
179
  - `GET /v1/projects/:project_id/memory/search?q=...` — busca ranqueada por chunks, com trecho do
165
- match e proveniência; usa fallback lexical quando FTS5 não está disponível.
180
+ match e proveniência; usa fallback lexical quando FTS5 não está disponível e exige Bearer.
166
181
  - `GET /v1/projects/:project_id/sync` — modo, contagem, conflitos e último evento.
167
182
  - `PUT /v1/projects/:project_id/sync` — compatibilidade de configuração; a autoridade continua SQL.
168
- - `GET /v1/projects/:project_id/memory/export` — exportação read-only com conteúdo completo.
183
+ - `GET /v1/projects/:project_id/memory/export` — exportação read-only sanitizada por padrão; exige Bearer.
169
184
  - `POST /v1/projects/:project_id/memory/events` — ingestão idempotente em lote.
170
185
  - `GET /v1/projects/:project_id/usage/summary` — totais filtráveis por período, change, sessão,
171
186
  agente, provedor, modelo e papel.
172
187
  - `GET /v1/projects/:project_id/usage/breakdown` — hierarquia de agentes, subagentes e modelos.
173
- - `GET /v1/projects/:project_id/usage/calls` — chamadas individuais com prompt e resposta.
174
- - `GET /v1/projects/:project_id/transcripts/:transcript_id` — transcript comprimido, validado por hash.
188
+ - `GET /v1/projects/:project_id/usage/calls` — chamadas individuais com prompt e resposta; exige Bearer.
189
+ - `GET /v1/projects/:project_id/transcripts/:transcript_id` — transcript comprimido, validado por hash; exige Bearer.
190
+ - `GET /v1/projects/:project_id/security` — policy, contagens de tokens e audit sanitizado; exige admin.
191
+ - `PUT /v1/projects/:project_id/security/policy` — atualiza a policy efetiva sem restart; exige admin.
192
+ - `POST /v1/projects/:project_id/security/purge` — dry-run/purge transacional com receipt; exige admin.
175
193
 
176
194
  As rotas `/v1` rejeitam corpo transportado ou expandido acima do limite e validam projeto, caminho,
177
195
  revisão, hash, idempotência e isolamento antes de gravar o conteúdo no SQLite. Para preservar uma
@@ -88,6 +88,12 @@ mesmo diretório e rename atômico. No deep, `verificacao.json` e `verdict.json`
88
88
  `evidenceEnvelopeId` e `evidenceBinding` completo; o verificador independente deve preservar ambos
89
89
  no verdict.
90
90
 
91
+ Para artefatos de bridge, `.wendkeep/bridge-artifacts.json` e os arquivos referenciados precisam
92
+ estar versionados e iguais ao índice Git. O sensor ligado à mesma task declara `artifact_results`
93
+ v1 em `wendkeep.sensors.json`; após GREEN, o runner calcula o SHA-256 diretamente dos bytes e grava
94
+ o resultado estruturado no sensor. `verify` cruza manifest, task, sensor, digest e blobs Git antes
95
+ de preencher `external_artifacts`; stdout, transcript e `output_tail` nunca são prova do arquivo.
96
+
91
97
  O envelope carrega `tdd_attestations`, e `verificacao.json`, `tddAttestations`. Em GOVERN, uma tarefa marcada
92
98
  `[tdd]` precisa de GREEN atual ou waiver explícito; em ASSURE, isso vale para comportamento
93
99
  testável. Refactor/commit posterior ou mutante sobrevivente invalida o GREEN no Task Contract.
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { pathToFileURL } from 'node:url';
3
3
  import { debugLog, readHookInput, resolveVault } from './obsidian-common.mjs';
4
- import { publishObserverSnapshot } from '../src/observer-publish.mjs';
4
+ import { publishObserverSnapshot, resolveObserverPublisherSecurity } from '../src/observer-publish.mjs';
5
5
 
6
6
  async function main() {
7
7
  const input = readHookInput();
8
8
  const resolved = resolveVault(input);
9
+ const publisherSecurity = resolveObserverPublisherSecurity();
9
10
  const result = await publishObserverSnapshot({
10
11
  vaultBase: resolved.base,
11
12
  projectRoot: resolved.projectRoot,
12
13
  input,
14
+ ...publisherSecurity,
13
15
  });
14
16
  if (!result.ok && result.error) debugLog('Observer publish fail-open:', result.error);
15
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.87.0",
3
+ "version": "0.89.0",
4
4
  "description": "Vault-first persistent memory for AI coding agents, with an optional profile-aware governance runtime: OFF, FLOW, GUIDE, GOVERN, or ASSURE. Local-first and agent-agnostic (Claude Code, Codex, Cursor…).",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -46,6 +46,7 @@
46
46
  "scripts": {
47
47
  "precheck": "node --check src/capabilities.mjs && node --check src/host-capabilities.mjs && node --check src/task-contracts.mjs && node --check src/task-leases.mjs && node --check src/task.mjs && node --check src/change.mjs && node --check src/archive-operation-lock.mjs && node --check src/worktree.mjs && node --check src/worktree-cleanup.mjs && node --check src/provenance-gate.mjs && node --check src/provenance-sources.mjs && node --check src/receipt-ledger.mjs && node --check src/evidence-envelope.mjs && node --check src/context.mjs && node --check src/active-context-health.mjs && node --check src/active-context-runtime.mjs && node --check hooks/active-context-store.mjs && node --check hooks/change-core.mjs && node --check hooks/brain-inject.mjs && node --check hooks/change-context.mjs && node --check hooks/session-stop.mjs && node --check packages/vault/src/worktree-metadata.mjs && node --check packages/vault/src/evidence-envelope.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/integrations/src/capabilities.mjs && node --check packages/pi/src/index.mjs && node --check src/sync-protocol.mjs && node --check src/sync-outbox.mjs && node --check src/sync-adapters.mjs && node --check src/sync-protocol-cli.mjs && node --check packages/mcp/src/sync.mjs && node --check src/portable.mjs && node --check src/tdd.mjs && node --check src/tdd-attestation.mjs && node --check src/tdd-attestation-store.mjs",
48
48
  "check": "node --check scripts/validate-commit-range.mjs && node --check packages/commit/src/index.mjs && node --check packages/commit/src/cli.mjs && node --check packages/commit/src/git-runtime.mjs && node --check src/git-commit-hooks.mjs && node --check scripts/release.mjs && node --check scripts/release-plan.mjs && node --check scripts/release-provenance.mjs && node --check scripts/run-scope.mjs && node --check src/release-provenance.mjs && node --check bin/wendkeep.mjs && node --check packages/cli/src/index.mjs && node --check src/mcp.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/active-context-health.mjs && node --check src/project-vault.mjs && node --check src/observer-auth.mjs && node --check src/observer-privacy.mjs && node --check src/observer-snapshot.mjs && node --check src/observer-store.mjs && node --check src/observer-memory.mjs && node --check src/observer-memory-publish.mjs && node --check src/observer-sql-store.mjs && node --check src/observer-sql-migrate.mjs && node --check src/observer-sql-publish.mjs && node --check src/observer-transcript-store.mjs && node --check src/observer-server.mjs && node --check src/observer.mjs && node --check src/observer-publish.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check src/work-kind.mjs && node --check src/delivery.mjs && node --check web/observer/app.mjs && node --check hooks/observer-publish.mjs && node --check hooks/evidence-context.mjs && node --check hooks/active-context-handoff-evidence.mjs && node --check hooks/evidence-recall.mjs && node --check hooks/memory-scope.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/operating-profile-task-store.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/integrations/src/host-hooks.mjs && node --check packages/integrations/src/hook-envelope.mjs && node --check packages/integrations/src/prompt-content.mjs && node --check packages/integrations/src/transcript-usage.mjs && node --check packages/integrations/src/transcripts.mjs && node --check packages/integrations/src/session-identity.mjs && node --check packages/integrations/src/index.mjs && node --check packages/mcp/src/audit.mjs && node --check packages/mcp/src/cli.mjs && node --check packages/mcp/src/config.mjs && node --check packages/mcp/src/effects.mjs && node --check packages/mcp/src/executor.mjs && node --check packages/mcp/src/server.mjs && node --check packages/mcp/src/stdio.mjs && node --check packages/mcp/src/index.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-scope.mjs && node --check packages/vault/src/memory-candidate-policy.mjs && node --check packages/vault/src/evidence-recall.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
49
+ "postcheck": "node --check packages/observer/src/index.mjs && node --check packages/observer/src/policy.mjs && node --check packages/observer/src/redaction.mjs && node --check packages/observer/src/authz.mjs && node --check packages/observer/src/token-registry.mjs && node --check packages/observer/src/encryption.mjs && node --check packages/observer/src/retention.mjs && node --check packages/observer/src/purge.mjs && node --check packages/observer/src/audit.mjs && node --check src/ecosystem-bridges.mjs && node --check src/ecosystem-bridge-artifact-collector.mjs && node --check src/ecosystem-bridge-baseline.mjs && node --check src/ecosystem-bridge-proof.mjs && node --check packages/integrations/src/bridge-config.mjs && node --check packages/integrations/src/bridge-contract.mjs && node --check packages/integrations/src/bridge-diagnostics.mjs && node --check packages/integrations/src/canonical-bridge-authority.mjs && node --check packages/integrations/src/ecosystem-bridge.mjs && node --check packages/integrations/src/spec-kit-adapter.mjs && node --check packages/integrations/src/superpowers-adapter.mjs",
49
50
  "test": "node --test --test-concurrency=2",
50
51
  "test:core": "node scripts/run-scope.mjs core",
51
52
  "release": "node scripts/release.mjs",
@@ -61,6 +61,7 @@ Usage:
61
61
  serve: --vault P · --timeout-ms N.
62
62
  config: --client generic|claude|codex|cursor · --vault P.
63
63
  wendkeep capabilities [...] Show declared lifecycle/effect coverage by host. --host <id> · --host-version <v> · --json.
64
+ wendkeep bridge <sub> Optional ecosystem adapters: status | import-spec-kit | export-status | dispatch-superpowers | verify-artifacts.
64
65
  wendkeep observer <sub> Local multi-project Observer: serve | register | publish | reconcile | status.
65
66
  wendkeep worktree create <slug> [--base ref] [--branch name] [--open vscode|none] [--json]
66
67
  wendkeep worktree list [--json]
@@ -237,6 +238,9 @@ async function main(argv) {
237
238
  } else if (cmd === 'commit') {
238
239
  const { COMMIT_HELP } = await import('../../commit/src/cli.mjs');
239
240
  process.stdout.write(COMMIT_HELP);
241
+ } else if (cmd === 'bridge') {
242
+ const { BRIDGE_HELP } = await import('../../../src/ecosystem-bridges.mjs');
243
+ process.stdout.write(BRIDGE_HELP);
240
244
  } else {
241
245
  process.stdout.write(HELP);
242
246
  }
@@ -250,7 +254,7 @@ async function main(argv) {
250
254
  // `sync` starts with `init` and resolves the freshly bound Vault itself. Pre-resolving
251
255
  // here would prevent that repair step from reporting a corrupt binding as its own
252
256
  // first-stage failure (and could never make it as far as the guarded init).
253
- && !['init', 'sync', 'worktree', 'hook', 'observer', 'mcp', 'capabilities', 'commit', '--version', '-v', '--help', '-h', 'help'].includes(cmd)) {
257
+ && !['init', 'sync', 'worktree', 'hook', 'observer', 'mcp', 'capabilities', 'commit', 'bridge', '--version', '-v', '--help', '-h', 'help'].includes(cmd)) {
254
258
  await preferProjectVault(rest);
255
259
  }
256
260
  switch (cmd) {
@@ -294,6 +298,11 @@ async function main(argv) {
294
298
  process.exit(runCapabilities(rest));
295
299
  break;
296
300
  }
301
+ case 'bridge': {
302
+ const { runEcosystemBridge } = await import('../../../src/ecosystem-bridges.mjs');
303
+ process.exit(runEcosystemBridge(rest));
304
+ break;
305
+ }
297
306
  case 'worktree': {
298
307
  const { runWorktree } = await import('../../../src/worktree.mjs');
299
308
  process.exit(await runWorktree(rest));
@@ -3,12 +3,13 @@
3
3
  // at the PROJECT ROOT (wendkeep.sensors.json); evidence lives per-change in the vault.
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import { createHash } from 'node:crypto';
6
- import { existsSync, readFileSync } from 'node:fs';
7
- import { dirname, join, resolve } from 'node:path';
6
+ import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
7
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
8
8
 
9
9
  export const SENSOR_VAULT_ENV = 'WENDKEEP_SENSOR_VAULT';
10
10
  const SENSOR_OUTPUT_MAX_BUFFER = 8 * 1024 * 1024;
11
11
  const SENSOR_DIAGNOSTIC_MAX_LENGTH = 2000;
12
+ const SENSOR_ARTIFACT_MAX_BYTES = 1024 * 1024;
12
13
 
13
14
  function sanitizeSensorDiagnostic(value) {
14
15
  return String(value || '')
@@ -27,6 +28,41 @@ function sha256(value) {
27
28
  return `sha256:${createHash('sha256').update(String(value || '')).digest('hex')}`;
28
29
  }
29
30
 
31
+ function containedPath(root, target) {
32
+ const rel = relative(root, target);
33
+ return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
34
+ }
35
+
36
+ function collectSensorArtifactResults(sensor, cwd) {
37
+ const declarations = sensor?.artifact_results;
38
+ if (declarations === undefined) return [];
39
+ if (!Array.isArray(declarations)) throw new Error('artifact_results must be an array');
40
+ const root = realpathSync(resolve(cwd || '.'));
41
+ const seen = new Set();
42
+ return declarations.map((declaration) => {
43
+ const externalId = String(declaration?.external_id || '').trim();
44
+ const configuredPath = String(declaration?.path || '').trim();
45
+ if (Object.keys(declaration || {}).some((key) => ![
46
+ 'schema_version', 'external_id', 'path', 'algorithm',
47
+ ].includes(key)) || declaration?.schema_version !== 1 || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(externalId)
48
+ || !configuredPath || declaration?.algorithm !== 'sha256' || seen.has(externalId)) {
49
+ throw new Error('artifact result declaration is invalid');
50
+ }
51
+ seen.add(externalId);
52
+ const candidate = resolve(root, configuredPath);
53
+ if (!containedPath(root, candidate) || !existsSync(candidate) || !lstatSync(candidate).isFile()) {
54
+ throw new Error(`artifact result path is unavailable: ${externalId}`);
55
+ }
56
+ const file = realpathSync(candidate);
57
+ if (!containedPath(root, file) || lstatSync(file).size > SENSOR_ARTIFACT_MAX_BYTES) {
58
+ throw new Error(`artifact result path is unsafe: ${externalId}`);
59
+ }
60
+ const path = relative(root, file).replaceAll('\\', '/');
61
+ const digest = createHash('sha256').update(readFileSync(file)).digest('hex');
62
+ return { schema_version: 1, external_id: externalId, path, algorithm: 'sha256', digest };
63
+ });
64
+ }
65
+
30
66
  function sensorNow(now) {
31
67
  const value = typeof now === 'function' ? now() : (now || new Date().toISOString());
32
68
  if (value instanceof Date) return value.toISOString();
@@ -167,7 +203,17 @@ export function runSensors(sensors, ids, { spawn = spawnSync, cwd, env, now } =
167
203
  output_sha256: sha256(rawOutput),
168
204
  output_tail: boundedOutputTail,
169
205
  };
170
- if (entry.status === 'red') entry.note = sensorFailureNote(r);
206
+ if (entry.status === 'green' && s.artifact_results !== undefined) {
207
+ try {
208
+ entry.artifact_results = collectSensorArtifactResults(s, cwd);
209
+ } catch (error) {
210
+ entry.status = 'red';
211
+ entry.exit_code = 1;
212
+ entry.artifact_results = [];
213
+ entry.note = sanitizeSensorDiagnostic(error?.message || 'artifact result collection failed');
214
+ }
215
+ }
216
+ if (entry.status === 'red' && !entry.note) entry.note = sensorFailureNote(r);
171
217
  if (s.type === 'mutation' && s.report) {
172
218
  // Delegated mutation (Wave B): read the tool's mutation-testing-elements report and
173
219
  // attach surviving mutants so verify can turn them into fix tasks.