Skip to content

Promote develop to main — the Core↔Tracker integration backend, closed end to end - #133

Merged
beyondnetPeru merged 77 commits into
mainfrom
develop
Aug 3, 2026
Merged

Promote develop to main — the Core↔Tracker integration backend, closed end to end#133
beyondnetPeru merged 77 commits into
mainfrom
develop

Conversation

@beyondnetPeru

Copy link
Copy Markdown
Contributor

Promotes everything accumulated on develop to main: 75 commits.

What this session closed

Four cards fully closed, and the thread joining them is the same defect in different places: surfaces that did not talk to each other.

Card What it was
CP-02 The Core-derived catalogue existed and nothing operational read it. Now four surfaces read one catalogue, with a per-tenant overlay that never edits the base standard.
CP-04 The context sent to the Core was a minimal shape. Now it carries schemaVersion, requester, repositoryRevision and the phase package — assembled by the server, not the caller.
CP-06 The gate decided from the Core's verdict alone; nothing consulted tenant configuration. Now the tenant decides which signals block, defaulting to advisory.
CP-14 SetItem mutated in place, so every edit erased the previous one. Now versions are immutable, restorable, and an approved one is sealed.

Also advanced: CP-05 (the canonical result stops dying at the ACL), CP-07 (a deposit hangs off the SDLC record and runs through the tenant matrix), CP-16 (the initiative ledger, derived rather than stored), CP-10 (core-sdlc-parity — verified against a live local deployment: the same signal blocks or not by configuration alone).

Two of the defects found were in pieces built earlier in this same session.

A false green worth naming

The scorecard's facts came only from what was delivered, so an artifact the standard requires and nobody delivered was invisible. A phase with an empty record scored null — "nothing demandable yet" — while the catalogue asked for three things.

Verification

Every commit reached develop through its own PR with the full check set green, including the Deploy (kind + Helm + smoke) job that runs the RoboSoft fleet. Locally, against a real kind deployment: 211 checks across the nine CI robots, plus 27 for the new parity robot, zero failures.

🤖 Generated with Claude Code

beyondnetPeru and others added 30 commits August 1, 2026 19:26
…abierta

Actualice la seccion de detalle al hacer el trabajo y me deje el ESTADO, tanto en la
fila del registro maestro como en la cabecera de la ficha. Una fila que reclama
trabajo ya hecho desvia las prioridades de la semana siguiente, que es exactamente lo
que he corregido tres veces hoy en el board del Core.

`docs/artifacts/CORE_ARTIFACT_SCHEMAS.md` enlaza los 14 artefactos que publican
esquema canonico a su `$id`, lista los 3 que son salida de herramienta y nombra los 7
que no tienen ninguno. Aguas arriba, `evolith_arch32#378` llevo la cobertura de 10 a
17 de 24 y arreglo las rutas `schemaRef` rotas.

Marcar la fila como resuelta obliga a REORDENAR: el validador exige el orden
BLOCKED → OPEN → DEFERRED → RESOLVED, y GAP-020 estaba en el septimo puesto, entre
las abiertas. Se reordena de forma estable —conservando el orden relativo dentro de
cada grupo— y se renumera la columna `#`.

Verificado que el registro no perdio nada: mismos 107 IDs antes y despues, mismo
conjunto exacto, cero duplicados. Resultado: 13 OPEN, 1 DEFERRED, 93 RESOLVED.

`validate-docs` pasa sobre 359 ficheros; `check-bilingual-parity`, `doc-inventory
--check` y `check-gap-registry` (203 fichas / 203 filas) en verde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cerrar GAP-020: el trabajo está hecho y la fila seguía abierta
…la nunca se movio

No es una refutacion parcial: es un gap CERRADO POR SUPERSESION con ADR aceptado.

`T-047` (Aceptado 2026-07-19) ratifica la topologia consolidada de cuatro esquemas,
supersede a `T-028` (schema-per-context, 10 esquemas) y **nombra a COH-005 entre los
gaps que cierra**, en su propio frontmatter. Nadie movio la fila.

`tracker_audit` no se creo A PROPOSITO. Los agregados de auditoria viven en
`tracker_governance`, donde funcionan: `audit_entries` se consulta en produccion,
`GT-603` tipo su actor, y `GT-588` le enchufo hoy los statements firmados de
transparencia.

El propio diseno de datos lo dice, y esto es lo que convierte la ficha en un
malentendido y no en una discrepancia: sus secciones 3-12 son «the original 2026-06-07
TARGET design ... kept for traceability only ... read them as intent, never as a
description of the database».

No faltaba capacidad. Un documento y el codigo discrepaban, y el ADR ya habia decidido
cual gana. Mover una tabla que funciona, con datos de produccion y un ledger firmado
encima, para satisfacer un diagrama que su propio documento etiqueta como intencion,
seria trabajo al reves.

Marcarla resuelta obliga a reordenar (BLOCKED → OPEN → DEFERRED → RESOLVED). Se
reordena de forma estable y se renumera. Verificado que no se pierde nada: mismos 107
IDs, mismo conjunto exacto, cero duplicados. Resultado: 12 OPEN, 1 DEFERRED,
94 RESOLVED.

Verificado: `validate-docs` sobre 359 ficheros y `check-gap-registry` en verde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
COH-005 estaba resuelta por decisión desde julio y la fila nunca se movió
…te, existe

La ficha afirma que `DeploymentRecord` «NEVER created by any story» y que UC-005b
(«records a DeploymentRecord») no se puede cumplir. Comprobado contra el codigo el
2026-08-01, las dos afirmaciones son falsas:

  agregado    Tracker.Domain/Release/DeploymentRecord — Start, Finish, RollBack
  tabla       tracker_release.deployment_records
  endpoints   POST /, POST /{id}/finish, POST /{id}/rollback
  tests       ConstructionReleaseTests

De modo que UC-005b es cumplible hoy. La fila describe un estado del repositorio que
dejo de ser cierto hace tiempo y nadie la movio.

Es la tercera ficha de esta sesion que reclama trabajo ya hecho — GT-588 criterio 3,
COH-005 y ahora esta. No es casualidad: el board recoge hallazgos de auditoria y nada
los vuelve a contrastar con el codigo, asi que envejecen en silencio y siguen dirigiendo
prioridades.

Se deja en la ficha el comando con el que se comprueba antes de reabrirla.

Marcarla resuelta obliga a reordenar; se reordena de forma estable y se renumera.
Verificado: mismos 107 IDs, mismo conjunto. Resultado 11 OPEN, 1 DEFERRED, 95 RESOLVED.

Verificado: `validate-docs` sobre 359 ficheros y `check-gap-registry` en verde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ables del board

En una sola sesion se encontraron CINCO fichas reclamando trabajo ya hecho: el
criterio 3 de GT-588 (cuatro sub-reglas y nueve tests negativos embarcados), COH-005
(cerrada por el ADR T-047 en julio, que la nombra), COH-008 (el `DeploymentRecord` que
dice inexistente tiene tabla, tres endpoints y tests), GAP-022 y GAP-025. Las cinco se
detectaron leyendo el codigo ANTES de empezar el trabajo. Quien se hubiera fiado de la
ficha habria construido cinco cosas que ya existian.

El board recoge hallazgos de auditoria y nada los vuelve a contrastar, asi que
envejecen en silencio mientras siguen dirigiendo prioridades. Este guard es esa
re-comprobacion que faltaba.

LO QUE NO HACE, Y ES LA DECISION DE DISENO: no lee la prosa de las filas. El
`50-validate-gap-claim` del Core ya corrio ese experimento — infirio intencion del
texto de los PRs, produjo nueve atribuciones falsas en un dia y enseno a los autores a
BORRAR referencias cruzadas verdaderas para aplacarlo. Aqui una afirmacion se comprueba
solo cuando alguien escribe, como DATO, los simbolos y las rutas cuya presencia la
refutaria.

SU SILENCIO NO PRUEBA NADA, y el guard lo dice en cada corrida imprimiendo su
denominador (2 declaradas de 107 filas). Puede demostrar que una afirmacion listada
dejo de ser cierta; nunca que una fila sigue siendolo.

UN DEFECTO PROPIO, ENCONTRADO EN SU PRIMERA CORRIDA REAL Y CONVERTIDO EN TEST: todas
las afirmaciones salian «refutadas» por las propias filas que las asertan y por los
informes de auditoria de los que vienen. Un documento que HABLA de un gap no es un
documento que lo CIERRA, y una sonda que no distingue las dos cosas refuta todo lo que
se le da — que es indistinguible de estar rota. `docs/audit` queda excluido por defecto
y hay un test negativo que lo fija.

Hallazgos de su primera corrida util, ya aplicados:

· GAP-022 → RESUELTA. `reference/specs/metrics/prd.md` lleva `REQ-MET-03`, que define
  las cinco metricas SPACE con criterios de aceptacion.
· GAP-025 → ESTRECHADA. El mapa estrategico existe y se autodenomina «the single,
  authoritative strategic map» de los 9 contextos; queda abierta solo la mitad de los
  cuatro contextos de soporte, y su sonda se RETIRA porque «cuales cuatro» pide criterio
  y una sonda que fingiera comprobarlo refutaria la fila con cualquier documento que
  mencione un contexto.

7 self-tests, cinco de ellos negativos. Cableado al job `gap-registry` junto a su
propio self-test.

Verificado: registro intacto (mismos 107 IDs), `validate-docs`,
`check-bilingual-parity` y `check-gap-registry` en verde. Resultado del board:
10 OPEN, 1 DEFERRED, 96 RESOLVED.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
COH-008 está refutada: el agregado que dice que no existe, existe
…equisito si

La fila dice «Redis in Docker Compose Without Requirement». Comprobado contra el
repositorio el 2026-08-01, las dos mitades son falsas:

LA PREMISA NO EXISTE. No hay ningun fichero `docker-compose` en este repositorio, ni
Redis en los charts de Helm de `product/infra/helm/` —que son el despliegue real— ni
Redis en el codigo de aplicacion.

Y EL REQUISITO QUE DICE FALTAR ESTA DECIDIDO. `T-026` («Redis solo para soporte
operacional») esta registrado en `AdrRegistryEndpoints`: «Redis is operational support
only (cache/locks/jobs/idempotency); PostgreSQL is the system of record».

La fila pide un requisito que existe, sobre un fichero que no.

Es la sexta ficha de esta sesion que reclama trabajo hecho o describe un estado que
dejo de ser cierto. El guard `check-falsifiable-claims` que se anadio hoy existe por
esto, aunque esta no era declarable como sonda: su afirmacion es sobre la AUSENCIA de
un fichero de infraestructura, no sobre la presencia de un simbolo.

Marcarla resuelta obliga a reordenar; reordenamiento estable y renumeracion.
Verificado: mismos 107 IDs, mismo conjunto. Resultado 9 OPEN, 1 DEFERRED, 97 RESOLVED.

Verificado: `validate-docs` y `check-gap-registry` en verde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GAP-021 refutada dos veces: el fichero no existe y el requisito sí
…ea que tenia era falsa

`GAP-015` pide el racional tecnico de T-001. Verificarlo produjo un segundo hallazgo
que la fila no menciona: **la unica linea que la entrada SI tenia era inexacta.**

Decia «Inicializado en `src/` utilizando npm workspaces con Nx». Ningun `package.json`
de este repositorio declara un campo `workspaces` — comprobado sobre todos ellos. Nunca
se usaron. Nx orquesta por GRAFO DE PROYECTOS: `project.json` mas los plugins de
inferencia `@nx/vite`, `@nx/webpack`, `@nx/eslint` y `@nx/jest` declarados en
`src/nx.json`.

Eso importa mas que la ausencia del racional: quien leyera la entrada buscaria un array
`workspaces`, no lo encontraria, y concluiria razonablemente que el monorepo esta mal
configurado — cuando lo cierto es que esta configurado de otra manera.

El ADR registra las tres razones, en el orden que decidio:

1. El repositorio NO es homogeneo. `tracker-api` es .NET, y npm workspaces enlaza
   `node_modules` entre paquetes npm: no tiene opinion sobre un `.csproj` ni puede
   ordenar un `dotnet build` frente a un `vite build`. Habria cubierto tres proyectos de
   cuatro dejando fuera al mayor.
2. Inferencia antes que declaracion, porque una lista de targets escrita a mano deriva —
   el mismo modo de fallo que ya costo tiempo en `GT-647`, `GT-640` y `ADR T-038`.
3. `nx affected` vale lo que valgan sus aristas; sin ellas, «ejecuta lo que cambio»
   degenera en «ejecuta todo» y se va el valor que justificaba el monorepo.

Se registra tambien el COSTE, que un ADR sin contrapartidas es propaganda: Nx pineado en
`^22.7.5` hace de una subida mayor un evento de todo el repositorio.

La entrada de `DECISIONS.md` queda corregida en ambos idiomas y enlazada al ADR.

Verificado: `validate-docs`, `check-bilingual-parity`, `doc-inventory --check` y
`check-gap-registry` en verde; registro intacto (mismos 107 IDs). Resultado del board:
8 OPEN, 1 DEFERRED, 98 RESOLVED.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GAP-015: el racional de T-001, y la línea que sí tenía era falsa
…en ingles» estan en castellano

GAP-011 y GAP-017 dicen que el C4 y el Discovery Canvas no tienen version inglesa. El
fichero `.md` EXISTE en ambos casos y se autodescribe «English (this document)». Su
contenido esta en castellano.

Por eso las dos filas seguian siendo ciertas con `check-bilingual-parity` en verde: ese
guard compara que el par EXISTA y que las cabeceras cuadren, y ninguna de las dos cosas
ve el idioma.

MEDIDO: 9 de 143 documentos emparejados estan asi, y las filas solo nombraban dos.
Entre los otros siete estan `DECISIONS.md` (128 marcadores de castellano contra 1 de
ingles) y `MASTER_INDEX.md`, canonicos de raiz.

COMO SE DETECTA: frecuencia de stopwords, con MARGEN de 2x y nunca por mayoria simple.
El margen se eligio midiendo — los nueve casos reales estan en 10x o mas y el documento
legitimo mas cercano ronda la paridad; una mayoria simple habria marcado glosarios
bilingues. Se retira el codigo antes de contar, porque un fichero lleno de
identificadores en castellano no es un documento en castellano.

VA COMO TRINQUETE: los nueve se declaran con su motivo, asi que CI pasa hoy y el DECIMO
falla. La regla escrita en el propio fichero dice que «no es prioritario» no es un
motivo. Comprobado que muerde: copiando un documento en castellano como `.md` se pone
rojo nombrandolo, y vuelve a verde al retirarlo.

LIMITACION QUE CONVIENE SABER: va en `docs.yml`, que solo se dispara en PRs a `main`
—como todos los guardianes de doc de este repositorio—, asi que muerde al PROMOVER y no
en el PR que introduce el fichero. Es coherente con el resto y se deja dicho en vez de
descubrirse dentro de un mes.

Verificado: `validate-docs` y `check-gap-registry` en verde; YAML del workflow validado.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ratchet-v2

El guard de paridad no ve el idioma: nueve documentos «en inglés» están en castellano
…sion ES

La fila dice «14 Technical Design Docs Lack ES Version». Medido el 2026-08-01:
`reference/specs/design/` tiene 20 documentos y TODOS tienen su `.es.md`. Cero faltan.

En todo el repositorio hay 19 ficheros `.md` sin pareja en castellano, y conviene mirar
CUALES antes de contarlos como deuda: NUEVE estan bajo `docs/audit/` —informes de
auditoria, triajes de corpus y este mismo registro—, que son documentos de trabajo y no
documentacion de producto. Los otros diez son READMEs y notas de tarea
(`robosoft/README.md`, `product/infra/helm/README.md`,
`Tracker.ArchitectureTests/README.md`, `docs/tasks/*`). Ninguno es un diseno tecnico.

LO QUE SI SOBREVIVE, y es distinto: tener la pareja no es tener la traduccion. El guard
`check-translation-language` anadido hoy encontro 9 documentos cuyo `.md` esta escrito
en castellano pese a existir el par — que es exactamente lo que describen GAP-011 y
GAP-017, y siguen abiertas.

Es la septima ficha de esta sesion que reclama trabajo hecho o describe un estado que
dejo de ser cierto.

Reordenamiento estable y renumeracion; mismos 107 IDs, mismo conjunto.
Resultado: 7 OPEN, 1 DEFERRED, 99 RESOLVED.

Verificado: `validate-docs` y `check-gap-registry` en verde.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GAP-013 refutada: los 20 diseños técnicos tienen su versión ES
The row asks for one business-rule id carrying two rules to be split. The
business-rule table already carries the split, and credits this row for it:
BR-010, CFR Quality Threshold, described as escindida de BR-003 por COH-009;
BR-003 es solo la firma humana.

Verified across the repository rather than from that one line: every BR-003
occurrence outside the audit tree means Human Sign-Off and nothing else, and
the QA-gate half is cited as BR-010 by reference/specs/qa, by the test
strategy against PhaseGateEvaluator, and by the Re-Do flow design.

This is the eighth row found describing work already done. Its claim is not
machine-falsifiable (it is about meaning, not presence), so it is NOT added
to falsifiable-claims.json: that file takes only what a probe can refute
without judgement, and a regex over prose is the failure mode it exists to
avoid.
docs(audit): close COH-009 — BR-003 conflation already split into BR-010
…1, GAP-017

Both rows were TRUE, and both had been marked DONE on the other board while the
defect was still there.

c4-macro-topology-phase1.md carried the banner English (this document) and 86 of
its 87 non-empty lines were byte-identical to the Spanish file: only the
navigation banner had been translated. DISCOVERY_CANVAS.md was 23 of 24. Both
passed check-bilingual-parity, which compares that the pair exists and that the
headers match, and can see neither language nor content.

Both are now genuinely English — prose, mermaid node descriptions and
relationship labels — and their entries are removed from
untranslated-allowlist.json, which drops from 9 declared to 7. That file is a
list of promises, so shrinking it is the point.

Verified after translating that no line still identical to the Spanish file
contains Spanish prose: the remaining overlap is fences, braces and identifiers.
docs(i18n): translate C4 topology and Discovery Canvas — close GAP-011, GAP-017
…12, GAP-016

COH-012 asked for the two missing CRUD operations. Nine scenarios added across
three Features: Create (type derivation for 0/1/2/7 products against
InitiativeScope.Derive, the Initiative.TenantRequired guard, ConsolidateFrom
lineage), Read (own-tenant hit; other-tenant read returning exactly what a
non-existent id returns) and withdrawal.

The row's premise is corrected, not obeyed. There is no Delete in this
aggregate and there must not be: an initiative is superseded into another
(Supersede -> consolidated, BR-DIS-005) because its lineage is governance
evidence. A Scenario: Delete would have satisfied the row, satisfied the probe
that was watching for exactly that string, and described a system that does not
exist. The COH-012 entry is withdrawn from falsifiable-claims.json with that
reason recorded in the file rather than deleted silently — a probe inherits
whatever its row assumed, and a row can assume something false.

Constants in the scenarios were read from the code, not guessed: TENANT_WIDE,
SINGLE_PRODUCT, CROSS_PRODUCT, consolidated, Initiative.TenantRequired,
Initiative.NotConsolidatable.

GAP-016 was stale. The roadmap has carried Document Status: SUPERSEDED since it
was corrected, and the marker cites this row by id.

Note on coverage: with COH-012 withdrawn and GAP-022 resolved, the
falsifiable-claim guard now probes ZERO claims and says so on every run. That is
the guard working as designed, not a pass.
docs(discovery): cover Create/Read/Supersede in Gherkin — close COH-012, GAP-016
…025, file COH-016

This repository keeps THREE gap surfaces sharing one id namespace: the register
(tracker-gaps-opportunities-tracking.md), the board (tracker-gap-tracking.md)
and the catalog (tracker-gap-reference-catalog.md). check-gap-registry.py kept
the last two in step since the day it was written, and never looked at the
first.

Measured today: 34 ids in common, 5 of them contradicting each other. And
neither surface was the trustworthy one — the board claimed GAP-011 and GAP-017
done while both files were still Spanish, and the register kept three rows open
that the catalog had closed with evidence in July.

Reconciled, each verified against the repository rather than against the other
board:
  GAP-023 -> RESOLVED. The 254-line Re-Do design exists with state model,
    recalculation algorithm, events and traceability. Its five open items are
    scheduling decisions awaiting PO ratification, not missing design.
  GAP-025 -> RESOLVED. bounded-context-map.md names the four supporting
    contexts and all ten ddd-model.md files exist.
  GAP-020 -> REOPENED. This corrects my own closure from earlier today. The
    catalog reopened it by product direction and the reason stands: the Tracker
    must CONSUME the Core schema references, not merely document them.

The guard now compares the two vocabularies on the only thing both state
unambiguously — closed or not — because inventing an OPEN->PENDING mapping
would be a decision dressed up as a check. Six self-tests; three go red when the
comparison is disabled, verified by disabling it.

COH-016 records the finding so the next reader does not rediscover it.
chore(audit): contrast the third gap surface — reconcile GAP-020/023/025, file COH-016
… GAP-020

The row was reopened by product direction with a precise complaint: a satellite
that only DOCUMENTS the link has not consumed it. CORE_ARTIFACT_SCHEMAS.md let a
reader find the canonical shape of a PRD; no machine could.

The correspondence now lives as data in CoreArtifactSchemaCatalog, and every
artifact served by GET /api/phase-artifact-profiles carries a canonicalSchema
with the published $id. The artifact-fields screen renders it as a link.

provenance has THREE values and not a nullable id, because two of them are
different absences: core-schema, tool-output (the Core deliberately publishes
none — CI run records, coverage reports) and none (nothing upstream; the
tenant's ArtifactFieldSchema is the only authority). Collapsing the last two
into null would erase the difference between a gap someone should close and a
decision already taken.

The mapping is a WRITTEN DECISION, not a slug transformation. A matcher would
miss security-scan-result vs Security Scan Report, rollback-plan vs Rollback
Procedure and observability-readiness vs Observability Validation, and would
miss them silently. So CoreArtifactSchemaCatalogTests parses the markdown table
and compares BOTH ways: a documented row nobody wired fails, and a wired entry
nobody documented fails too. Verified by breaking it in both directions rather
than assuming.

adr-registry is deliberately NOT mapped to adr.schema.json: a registry is a list
of ADRs and not an ADR, so the mapping would validate green on paper and produce
a false negative against the real artifact.

DELIBERATELY NOT BUILT: hardwired validation of artifact content against these
schemas. The reopen text asks for it, and T-056 — ratified by the user — says
content validation is the tenant's configuration and not engine code. The
reference is exposed so a tenant's own configuration can point at it; enforcing
it here would make the Core the authority over what a tenant may record. Flagged
rather than silently skipped.

Local: 6/6 new tests, frontend lint+typecheck+build green. The 10 integration
failures on this machine are the DB-gated suite that fails by design without
PostgreSQL; CI provides it.
…four

Correcting my own bookkeeping before merging. The reopen asked for four things:
consume the references, persist or cache them tenant-aware, expose them in the
phase forms, and use them to validate what gets filled in.

This PR does the first and the third. Marking the row RESOLVED would be the
exact failure this session has spent its time undoing — a row that reads as done
while a stated requirement is unbuilt.

What remains is recorded on the row: tenant-aware persistence (arguably
unnecessary, since the references are Core-authoritative and tenant-agnostic,
but that judgement has not been ratified) and content validation, which collides
with T-056 and needs a product decision before anyone writes code for it.
…schemas

feat(governance): consume the Core artifact schema references — GAP-020 (2 of 4)
…020; unblock GAP-004's truth

Started as GAP-020 bookkeeping and turned into three rows that were lying,
found by extending the coherence guard to a fourth check.

GAP-020 — closed. Two of the four asks shipped in #108. Content validation is
WITHDRAWN by product direction (2026-08-02): T-056 rules, so the Tracker offers
the canonical shape and never enforces it, and the schema document now says so
as a ratified decision rather than an omission. Tenant-aware persistence is
handed to GAP-004: without validation a cache has no consumer, and the version
that matters is syncing the catalog from the Core, which is the
IPhaseArtifactProfileSource seam GAP-004 owns.

COH-006 / COH-007 — closed by fixing the DOCUMENTS, not by building tools. Both
rows claimed tracker-gateway exposes 6/6 Tracker MCP tools. It does not, and its
controller says so: the Core remains the single agent-facing MCP server, this is
a client of it. History: built (c5f3a50), rebound to the BFF (293e3de, T-051),
DELETED (614ddff) when T-052 made the Core's evolith-mcp the single surface. So
the original finding was live again in its own terms — documents promising a
surface that does not exist — with a different remedy. Six documents aligned:
PRD 5.3 (EN+ES) rewritten with where each capability is served now, the
local-dev guide (EN+ES) whose smoke command pointed at a 404, and banners on
ux-concept, blueprint and tracker-agent-assignment-api. Text kept rather than
deleted: the capabilities are real and served by the BFF; only the transport is
gone.

GAP-004 — corrected to BLOCKED, matching the card that was right all along. It
had been closed with 'Resolved via Defensive Isolation (Mocks)'. Installing a
stand-in is not resolving a dependency, and the code agrees: the stand-in is
still the only implementation and every profile it serves is stamped
core-standin.

The guard now contrasts row against card inside the register — 3 of 108
disagreed, all three with the row claiming closure. Three new tests; the new
check goes red when disabled, verified by disabling it.
…t052

docs(mcp): align six documents to T-052 — close COH-006/COH-007, GAP-020; correct GAP-004
…am as GT-650

The row said 'Define owner and remediation action before execution' and nobody
ever had. Each of the three upstream dependencies was probed today rather than
assumed.

Core API. The root cause is not a missing endpoint, it is that the Core has no
single answer to endorse: two unreconciled artifact corpora that disagree on
names, on membership in both directions, and on phase — Coverage Report is a
construction artifact in one and a quality artifact in the other — while the
HTTP surface serves a third file with no artifacts at all. Filed upstream as
evolith_arch32 GT-650 (PR #383) with five acceptance criteria. Cannot be
unblocked from this repository.

UMS JWKS. Still blocked; T-053 is still Proposed. Probed today: the local UMS
ingress answers 503 on every path, so nothing could even be re-tested. The
Tracker already carries both validation modes, so this blocks the target state
rather than the product.

UMS Auth Graph. Same system, same reachability, still blocked.

Also recorded: the residue of GAP-020 lands here, because tenant-aware sync of
the artifact catalog is the same seam and the same blocker.
docs(audit): scope GAP-004 blocker by blocker; Core half filed upstream as GT-650
beyondnetPeru and others added 27 commits August 3, 2026 09:11
Closes CP-05 criterion 1.

The finding was better than the card's statement. The problem was not missing
columns: `correlation_id`, `schema_version`, `overall_verdict`, `outcome`,
`rules_executed`, `evaluated_at` and `versions` already existed, but only the
INGEST path filled them. A row born in the gateway left them empty even when the
Core's response carried the values.

A column that exists and is empty for half the population is worse than no
column, because whoever queries it cannot tell "there was none" from "this path
did not store it".

`CompleteFromCore` fills them and stamps `producer_surface = tracker-gateway`,
so a gateway row and a CLI-deposited one are distinguishable in the table itself
rather than by guesswork.

What has no column of its own -- policiesApplied, risks, recommendations,
requiredActions, qualitySignals, the result kinds -- is preserved in the
`response_data` jsonb that already existed. NO MIGRATION AND NO SPECULATIVE
SCHEMA: the typed columns that exist were typed when somebody needed to query
them (route by owner, count blockers). When criterion 2's UI says what it
actually queries, that gets typed and not before.

`CoreEvaluationDeposit` was deliberately NOT reused. It is the INGEST shape and
requires violations with a fingerprint and an accountable owner, which the
gateway never receives; filling those here would be exactly what that shape
forbids in writing -- inventing an accusation.

Two judgement calls, both with a test:
  - a rule with no engine is RECORDED as `unknown`, not dropped. Dropping it
    would erase the evidence that the rule ran; saying "I do not know which
    engine" is true and checkable. Coercing it to a known engine is the one
    unacceptable option, and the contract says so itself.
  - `correlation_id` stays NULL when the Core issued none. Copying the Tracker's
    `operation_id` into it would add no information -- it already has its own
    column -- and would make a Core-issued id indistinguishable from ours.

Verified: 6/6 new, full suite 1200 passed / 10 failed, and the 10 are the same
environment-dependent set that fails on a clean worktree at HEAD. Zero new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…re-facts

feat(ledger): a gateway-born row stops being born half empty
Closes CP-06 (all four criteria).

The Tracker decided phase advance from the Core's `overallVerdict` plus a count
of blocking gaps. Nothing consulted tenant configuration, so the authority
boundary was blurred in both directions: an optional technical signal could stop
a phase, and a contractual obligation of the tenant could sit there as a warning.

`CoreSignalRule` is a matrix per tenant AND phase, hung off the GatePolicy that
already carries the tenant's gate configuration. Dispositions: blocking,
advisory, hidden, not-applicable, with an optional minSeverity. It is configured
through the existing `PUT /gate-policies/{phase}` and persisted in three new
jsonb columns (additive migration, with a Down).

Six decisions, each with a test that fails against the alternative:

  - THE DEFAULT IS ADVISORY. A signal with no rule is reported and does not
    block. The opposite default would turn every new dimension the Core
    publishes into a block no tenant asked for -- the product forming opinions
    of its own, which T-056 forbids. Erring toward "does not block" is
    recoverable by reading the report; erring toward "blocks" on a phase in
    flight is not.
  - THE MOST SPECIFIC RULE WINS, not the first in the list. Ordering by position
    would leave the outcome at the mercy of how a tenant sorted their list,
    which is not a decision anyone consciously made. Tested in BOTH orders --
    with only one, a first-match-wins evaluator passes.
  - AN UNKNOWN SEVERITY IS NOT COERCED. Raising it would fabricate blocks and
    lowering it would hide findings, so it falls outside min-severity rules and
    stays visible through the default path.
  - A GATE WITH NO MATRIX BEHAVES EXACTLY AS BEFORE. Without this, landing the
    card would have changed the behaviour of every already-configured gate
    without any tenant asking.
  - AN UPSERT WITHOUT `coreSignalRules` CLEARS THE LIST rather than keeping what
    was there. Preserving unsent rules would make removing a block impossible,
    and removing one is precisely what a tenant needs to do quickly.
  - AN INVALID DISPOSITION IS REJECTED ON WRITE. Storing it and ignoring it
    later would give a tenant who believes they configured a block and a gate
    that does not apply it.

Only `blocking` folds into unmet reasons; advisory/hidden/not-applicable are
recorded and never stop the phase. `AppliedCoreSignalRules` travels COPIED with
the submission rather than referenced -- a tenant's matrix changes, and without
that copy an audit would have to assume today's configuration is the one that
decided.

Not covered, stated rather than implied: tracker-web does not expose the matrix
yet, so it is configured through the API. And the caller still builds the
observations -- feeding the Core facts CP-05 now preserves in `Intelligence`
into `CoreSignalObservation` is the next stretch.

Caught in passing: the two new collections broke `PropsDeepCloneTests` (GT-470's
guard, which clones for real rather than grepping for `this with`). Both Props
clone hooks fixed.

Verified: 14 new tests; full suite 1214 passed / 10 failed, the same
environment-dependent set that fails on a clean worktree at HEAD. Zero new,
compared set-by-set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(governance): the tenant decides which Core signals block a phase
…l shape

Advances CP-04: criteria 1 and 4 closed, criterion 2 closed, criterion 3 unstarted.

The Tracker sent kinds, workspaceRef, phaseId and a passthrough bag. Everything
else it knew about a request -- who asked, which revision was judged, which
contract version it speaks -- either did not travel or travelled as untyped
strings the Core echoes without interpreting.

  - `schemaVersion` is a fact of the CALLER ("I speak this version"), not an
    assumption about the Core. The capability manifest was no help: its
    `schemaVersion` is the RESULT contract's. Configurable precisely because it
    is a copy of a constant that lives in the other repo -- the day the Core
    moves its own, this is adjusted rather than left quietly lying.
  - `requester` and `repositoryRevision` now travel TYPED. `repositoryRevision`
    leaves `passthrough`, which the Core echoes without interpreting: the fact
    was present and took no part in the evaluation. No copy left behind, because
    there was no consumer -- verified on all three sides: the Core reads only
    `evaluationDate` from passthrough, and neither the Tracker nor the web reads
    the passthrough it gets back.
  - the optional technical fields are gated OFF by default, and there is nothing
    to send today: the Tracker produces no structural facts and no quality
    signals. Sending them empty would tell the Core "I looked and found nothing"
    when the truth is "I did not look", and the Core reads absence as no
    structural findings, never as a failure.

Criterion 2 was IMPOSSIBLE until today. The Core runs a global ValidationPipe
with `forbidNonWhitelisted: true` and its `EvaluationContextDto` declared
neither field, so sending them would have 400'd the whole evaluation rather than
arriving stripped. Registered and fixed as GT-652 in evolith_arch32 (PR #401).

DEPLOY ORDER IS THEREFORE CORE FIRST -- stated rather than discovered.
`SendTypedContextFacts` (on by default) returns the payload to the previous
minimal shape, so a Tracker pointed at a pre-GT-652 Core is fixed with an
environment variable instead of a rollback. Both switch positions have a test.

Absence stays absence: with no known revision the field is omitted rather than
sent as `HEAD`, which would be indistinguishable from a real revision named HEAD.
`requester` is omitted with no actor -- an empty one would assert an attribution
we do not have. `actorType` is `user` because the Tracker authenticates people
and services only; claiming `agent` would be guessing.

Criterion 3 is untouched and says so on the card: `artifacts`/`evidence` ARE
accepted by the wire, but `EvaluateContextRequest` receives them from nobody --
the endpoint would have to take the phase record (the one CP-02 already
contrasts against the Core catalogue) and translate it.

Verified: 6 new tests, 16/16 in the class; full suite 1220 passed / 10 failed,
the same environment-dependent set that fails on a clean worktree at HEAD.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n-context

feat(integration): the context the Tracker sends stops being a minimal shape
Closes CP-04 criterion 3 — and with it, CP-04.

`artifacts` now travels in the shape the Core declares (`ArtifactContext`):
`required` from the Core-DERIVED catalogue CP-02 wired, `presented` from the
record people fill. It is the join between the two cards -- the same seam, not a
second copy. Sending only what was presented would leave the Core with nothing
to contrast it against; sending only what is required, with no idea what is there.

THE SERVER BUILDS IT, NOT THE CALLER. Both sources are injected into the gateway
and `EvaluateContextRequest` still has no artifact field -- there is a test on
the TYPE that breaks if anyone adds one. If it travelled in the body, a caller
could declare an artifact that does not exist as presented, and the Core would
evaluate it as though it were there.

Three decisions, each with a test:

  - AN EMPTY SLOT IS NOT PRESENTED. Only items with a `ref` or a body travel;
    declaring a blank slot somebody opened in the form would tell the Core the
    artifact is there.
  - INLINE CONTENT DOES NOT LEAVE THE PERIMETER. The reference travels, never
    the body: the Core evaluates DECLARED facts, and dumping the document here
    would take out of the tenant something nobody asked to send.
  - WITH NEITHER HALF, THE BLOCK IS OMITTED. Two empty lists would assert "this
    gate requires nothing and nothing was presented", which is a different
    evaluation from "I could not assemble the package". Same reasoning for an
    opaque `initiativeId`, which the contract explicitly allows: there is no
    record to look up, so it says so rather than logging a failure for something
    permitted.

`evidence` is deliberately NOT sent. In the Tracker, evidence is the
`GateSubmission` slots -- a different aggregate this endpoint does not hold.
Sending the record's items under that key would rename them to a concept they
are not.

Wiring verified, not assumed: the repository is Scoped and the typed client is
transient, so a test resolves the gateway INSIDE a scope and checks the container
fills both dependencies. Confirmed by removing the registration and watching it
fail. "Built but not wired" is the defect this campaign keeps finding.

Verified: 8 new tests; full suite 1228 passed / 10 failed, the same
environment-dependent set that fails on a clean worktree at HEAD. Zero new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(integration): the phase package travels, and the server builds it
Closes CP-07 criterion 1.

`GT-604` gave the Core a place to deposit a verdict it produced. The row was
auditable as a technical ledger and appeared nowhere near the phase somebody was
trying to close: no typed link to an initiative, a phase, an artifact or a gate
submission.

WHY A LINK AND NOT A FIELD ON THE DEPOSIT. `EvaluationIngestPayload` carries
neither initiative nor phase, and should not: whoever deposits is the CLI, the
MCP or the drift gate, and none of them knows which Tracker initiative the thing
they evaluated belongs to. Attaching is therefore a governance decision taken
afterwards by somebody who does know.

Five decisions, each with a test:

  - ATTACHING DOES NOT TOUCH THE VERDICT. `overallVerdict`, `outcome`,
    `violations` and the blocking count come out identical. It is what separates
    a link from a second opinion: a method that could adjust the verdict would
    stop the ledger being what the producer deposited.
  - IT DOES NOT CROSS THE TENANT BOUNDARY, in both directions. Attaching is the
    operation that joins two aggregates -- exactly where a leak between clients
    would slip in. Someone else's deposit answers NOT FOUND rather than
    forbidden, because telling them apart would make this endpoint a way to
    discover which ids exist in another client.
  - ITS OWN PERMISSION, `tracker:core-transaction:attach`, separate from ingest.
    Depositing is a MACHINE operation; attaching is a GOVERNANCE decision. The
    machine handler does not grant it, so a leaked key cannot hang evidence on
    the phase somebody is trying to close.
  - YOU CAN DETACH, NOT UN-DEPOSIT. Attaching to the wrong place is an ordinary
    human error and must be reversible; what is not undone is the record of what
    the Core said.
  - ABSENCE STAYS ABSENCE: a blank `artifactKind` stores as null. "It did not say
    which artifact" is not "it speaks about the artifact named empty-string".

Caught by the repo's own guard: `CoreEvaluationTransactionContractTests` pins the
emitted JSON field-for-field against `tracker-web/src/api/types.ts`, so adding
`attachment` reddened it until the TypeScript side declared it too. Both updated;
`tsc --noEmit` clean.

Verified: 9 new tests; full suite 1237 passed / 10 failed, the same
environment-dependent set that fails on a clean worktree at HEAD. Zero new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…atrix

Closes CP-07 criterion 3 — and the stretch CP-06 recorded as pending.

CP-05 preserves what the Core returns, CP-06 built the matrix that decides what
blocks, CP-07 let a deposit hang off a phase. Nothing produced a
`CoreSignalObservation`, so the matrix existed with no input: "built but not
wired", this time in pieces of my own from earlier in this campaign.

`CoreDepositSignals` translates an ATTACHED deposit into observations;
`CoreSignalGateEvaluator` runs them through the tenant's rules inside
`EvaluateGateSubmission`.

It TRANSLATES, it does not decide -- no thresholds, no severity normalisation.
Five decisions, each with a test:

  - ONLY ATTACHED DEPOSITS ENTER, filtered by initiative AND phase. A loose one
    speaks about no phase at all; one hanging off the same phase of a different
    initiative has nothing to say about this gate.
  - THE VIOLATION'S CATEGORY IS THE SIGNAL when there is one, which is how a
    tenant writes "architecture violations deposited on me block, style ones do
    not". With none it falls to `core-deposit`, a stable documented name --
    inventing a category would be guessing what it speaks about.
  - THE VERDICT IS OBSERVED even with no typed violations. Not redundant: a
    producer can return FAIL and nothing else, and without that observation the
    case would be invisible to the matrix.
  - A FROZEN VIOLATION IS NOT RE-OBSERVED. It is one the tenant already accepted
    knowingly, and the ledger already excludes it from its blocking count;
    counting it here would make two surfaces of the same product disagree about
    the same fact.
  - NO MATRIX CONFIGURED RETURNS NULL and the gate decides exactly as before,
    like the T-031 criteria alongside it. With a matrix and no deposits it DOES
    evaluate, with zero observations: "nothing blocked" is true and belongs in
    the trace, whereas null would erase from the audit that the matrix was
    active and found nothing.

Verified: 8 new tests; full suite 1245 passed / 10 failed, the same
environment-dependent set that fails on a clean worktree at HEAD. Zero new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(integration): a Core deposit can hang off the SDLC record
…h-matrix

feat(governance): attached deposits run through the tenant's signal matrix
…ndard

Closes CP-02 criterion 2 — the other half of that card's title.

`ArtifactApplicabilityRule` hangs off `GatePolicy`, which is already per tenant
and phase, and marks an artifact `required`, `optional` or `not-applicable`.

"WITHOUT MODIFYING THE BASE STANDARD" IS TRUE IN THE CODE, not just on the card.
`IPhaseArtifactProfileSource` stays tenant-agnostic: it serves what the Core says
and takes no tenant. The overlay is applied on top at serve time, in
`TenantPhaseArtifactCatalog`, so there is no path by which a tenant's
configuration edits the Core's catalogue. There is a test that applying the
overlay does not mutate the input profile.

Five decisions, each with a test:

  - MARKING IS NOT DELETING. A `not-applicable` artifact is still served, with
    the mark on it. Removing it would leave a reader unable to tell that the
    standard asks for it and this tenant waived it -- two different facts, both
    auditable.
  - WHO REQUIRES IT IS DISTINGUISHABLE. `tenantApplicability` and
    `standardRequired` travel separately; serving only the outcome would leave
    an auditor unable to separate "the Core requires it" from "this tenant
    requires it".
  - A MARK ON SOMETHING THE CORE DOES NOT DECLARE IS IGNORED. Adding
    tenant-specific artifacts is a different capability and a different card;
    accepting it here would turn the catalogue from "the Core's standard with an
    opinion on top" into two catalogues mixed with no way to tell them apart.
  - AN UPSERT WITHOUT THE OVERLAY CLEARS IT rather than keeping what was there:
    otherwise returning an artifact to the standard would be impossible.
  - AN INVALID APPLICABILITY IS REJECTED ON WRITE, for the same reason as in the
    signal matrix.

ALL THREE SURFACES READ THE SAME OVERLAID CATALOGUE, which was the real risk of
leaving this half-done: the profiles endpoint, the phase-record contrast
(criterion 1), and the `artifacts.required` sent to the Core during evaluation.
Sending the Core the bare standard while the Tracker governs with the overlay
would have been, once again, two surfaces disagreeing about the same fact.

Additive migration with a Down. Two existing test doubles moved to the
tenant-aware interface -- the intended consequence of the change, not collateral.

Verified: 8 new tests; full suite 1253 passed / 10 failed, the same
environment-dependent set that fails on a clean worktree at HEAD. Zero new.
`tsc --noEmit` clean on the web app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Advances CP-16: criteria 1, 2 and 4 closed; criterion 3 partial and said why.

The Tracker had a technical ledger for evaluations and scattered domain records,
and no business timeline explaining what happened to an initiative, who took
part, and what kind of fact each event was.

IT IS A PROJECTION, NOT A SECOND LEDGER. Every entry derives from an aggregate
that already holds it -- audit, gate submission, attached deposit -- and there is
no write path of its own. A parallel store would have its own writes and could
drift from the aggregates it summarises: the ledger would say one thing and the
submission another, with nothing to flag it. Deriving makes that divergence
impossible by construction, at the price of reading from three places.

Criterion 2 needed almost nothing built: the audit entry already carried
`ActorType`, `AgentId`, `ModelId`, `SessionId` and `CorrelationId`. The ledger
projects them rather than re-recording them.

Five decisions, each with a test:

  - THE TENANT IS CHECKED AT EVERY SOURCE, not just at the entrance. A query by
    initiative that trusted the id to belong to the right tenant would be a leak
    shaped like a listing.
  - A LOOSE DEPOSIT IS NOT THIS PHASE'S HISTORY. Only attached ones enter
    (CP-07): including one nobody placed there would count as the initiative's
    journey something that is not.
  - WHAT DOES NOT CLASSIFY FALLS TO `record` AND IS NOT FORCED. Marking as
    "blocked" something that merely resembles one by name would be inventing
    history on an audit surface.
  - THE CORE'S VERDICT IS NOT RE-CLASSIFIED AS A BLOCK HERE. What blocks the
    phase is the tenant's matrix (CP-06), whose dispositions already travel as
    their own entries; classifying it twice would duplicate the decision.
  - WITH NOTHING RECORDED THE LEDGER IS EMPTY. Filling gaps with assumptions
    would turn an audit trail into a narrative.

Criterion 3 stays open and says why on the card: artifact versions are CP-14 and
exports are CP-17, neither of which records anything yet. You cannot link what is
not written down.

Verified: 8 new tests; full suite 1261 passed / 10 failed, the same
environment-dependent set that fails on a clean worktree at HEAD. Zero new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(governance): the tenant marks artifacts without touching the standard
feat(sdlc): the initiative ledger, derived rather than stored
Advances CP-14: criteria 1, 2 and 4 closed; criterion 3 unstarted and said so.

`SetItem` mutated in place, so every edit erased the previous one. "Which version
was sent to the Core" and "which one was approved" had no answer.

`ArtifactVersion` lives INSIDE the aggregate, so an item and its history cannot
diverge -- a separate store would have its own write path and could disagree
with the item it describes.

The actor already travelled in `SetPhaseArtifactItemCommand` and was used for
nothing. It is now what turns an edit into traceable history.

Five decisions, each with a test:

  - A VERSION IS NEVER EDITED. What is immutable is the content and its
    authorship; the STATUS does transition, because moving to `superseded` is
    precisely the record that another replaced it. Conflating the two would mean
    either losing that record or duplicating it somewhere else.
  - RESTORING OPENS A NEW VERSION AND DOES NOT REWRITE THE PAST. What happened in
    between stays in the history: making it look like the edit never happened is
    exactly what an auditable record cannot do.
  - A SAVE THAT CHANGES NOTHING CREATES NO VERSION. Filling the history with
    noise would make the real change harder to find, which is what the history
    exists to allow.
  - NO AUTHOR, NO VERSION -- but the record still updates. Storing a half-filled
    version would give a history that looks traceable and is not.
  - A `ref` AND A `content` WITH THE SAME TEXT DO NOT SHARE A CHECKSUM. They are
    different things, and confusing them would claim two versions carry the same
    when they do not.

Criterion 4 holds by construction: an approved version moves to `superseded` and
the edit opens the next one. If approved content could change, the approval would
stop saying anything about what was approved.

RISK TAKEN AND STATED: versions live in the aggregate's `versions` jsonb, so
versioning inline content multiplies a row's volume. It is not a NEW class of
problem -- content is already stored inline in `items` -- but it does worsen it.
If the volume becomes real the way out is a per-version store; recorded on the
card rather than discovered as a 40 MB row.

Also corrected CP-16's note, which named CP-14 as a blocker for its criterion 3.
It no longer is: versions exist, and linking them in the ledger is a concrete
pending stretch rather than a dependency.

Verified: 10 new tests; full suite 1271 passed / 10 failed, the same
environment-dependent set that fails on a clean worktree at HEAD. Zero new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(sdlc): artifact versions, with a history that cannot be rewritten
Closes CP-14 criterion 3, and advances CP-16 criterion 3 to exports-only.

Versions existed but nothing referred to one. "Which version was approved" and
"which one did that evaluation judge" were still unanswerable the moment somebody
edited the artifact.

Three links, and one deliberate NON-link:

  - the deposit attachment (CP-07) carries `ArtifactVersion`;
  - approving a gate SEALS the live version of every artifact in the phase;
  - the ledger surfaces each version as an entry with author, reason and number.

WHERE THE VERSION DOES NOT GO: the payload sent to the Core.
`ArtifactContext.presented` does not declare a version, so sending one would
produce a field that arrives and nobody reads -- the same defect GT-652 closed in
the other direction. Whoever needs to answer "which version was evaluated" is the
Tracker's own record, and that is where it lives.

Four decisions, each with a test:

  - SEALING HAPPENS ONLY ON APPROVED. A RETURNED submission leaves the record in
    editing on purpose, and freezing versions there would stop the author redoing
    exactly what they were asked to redo.
  - IT WALKS WHAT THE RECORD ACTUALLY CARRIES, not what the gate demanded:
    approving the version of an artifact nobody delivered would assert it exists.
  - A MISSING RECORD IS NOT AN ERROR. A gate can be decided on evidence and
    approvals with no phase record; failing there would turn a valid approval
    into an error over something the model permits.
  - AN APPROVED VERSION IS A REQUIREMENT MET; EDITING IS ONLY A RECORD. If every
    version counted as met, the ledger would claim the artifact was satisfied
    every time somebody pressed save.

Verified: 3 new tests plus two existing suites moved to the new dependencies;
full suite 1274 passed / 10 failed, the same environment-dependent set that fails
on a clean worktree at HEAD. Zero new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(sdlc): approvals and evaluations point at a concrete version
…sible

Closes CP-02 criterion 3, and with it CP-02.

The scorecard's facts came ONLY from what was delivered (`artefacto.Items`), so
an artifact the standard requires and nobody delivered did not appear at all. A
phase with an empty record did not score badly -- it scored `null`, "nothing
demandable yet", while the tenant's catalogue asked for three things. The
dashboard said there was nothing to answer for exactly when everything was
missing.

`ScorecardProjection` now folds in the required artifacts from the SAME
tenant-aware catalogue that governs the phase record, the read-only endpoint and
the payload sent to the Core. Four surfaces, one catalogue.

WHAT WAS NOT TOUCHED, AND IS RIGHT: a required-but-undelivered artifact still
sits at `pendiente` while it is within its deadline, not red. Penalising before
the due date would be punishing in advance, and that rule was already well
reasoned. My first version of the test asserted the score moves immediately --
it was the assertion that was wrong, not the rule. The test now pins the moment
it must hurt: overdue and undelivered.

It scores against the catalogue WITH the overlay, not the bare standard.
Punishing a tenant for not delivering what they themselves marked
not-applicable would be the exact opposite of what CP-02 criterion 2 just
allowed them to do.

Verified: 4 new tests; full suite 1278 passed / 10 failed, the same
environment-dependent set that fails on a clean worktree at HEAD. Zero new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fix(scorecard): a required artifact nobody delivered stops being invisible
…onfiguration

Advances CP-10: criteria 3 and 4 closed; 1 partial and 2 unstarted, both said why.

`core-sdlc-parity` asserts the property no other robot covers and that four
cards were built for: the SAME attached deposit contributes nothing to the
verdict with no matrix, contributes `core-signal:architecture` when the tenant
marks it blocking, and contributes nothing again when marked advisory. Between
the three evaluations nothing about the deposit changed -- only the configuration.

VERIFIED FOR REAL, NOT IN CI. The full local stack was brought up
(`local-test.sh up`: kind + build + Helm) and the robot ran against it: 27
checks, 0 failures. The nine robots on the CI list ran against that same
deployment: 211 checks, 0 failures.

I had earlier written this row off as unverifiable. That was not a measurement --
docker, kind, kubectl and helm were all installed and a cluster already existed.
The user asked whether I had tried; I had not.

ONE TENANT, TWO CONFIGURATIONS -- stated rather than implied. The criterion says
"a tenant ... and another"; this runs it as ONE tenant reconfigured between
evaluations, because a deposit is scoped by the machine key's tenant and a second
one would need another key plus its own seeded initiative. The code path is
identical -- `CoreSignalRule` is stored per tenant AND phase -- but what it does
NOT prove is isolation between two tenants' matrices, which `tenant-isolation`
covers.

WHY IT IS NOT ON THE CI LIST: it needs a CoreMachine key bound to the tenant the
robot reads as, and `deploy-check.yml` configures none -- the same reason
`core-evidence-ingest`, `core-integration` and `runtime-approvals` are excluded.
Without the key it SOFT-SKIPS VISIBLY: the summary reports `0 ok`, not a silent
PASS. A robot claiming PASS having exercised nothing is the vacuous green this
repository keeps finding.

A defect in the robot itself, corrected: its first version asserted "the gate is
NOT blocked by the deposit" by checking the state was not RETURNED. The gate was
RETURNED over an unresolved approval unrelated to the deposit, so the assertion
measured something else. It now compares the unmet reasons with and without the
matrix: with the signal advisory the gate is EXACTLY as it was with no matrix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
test(robosoft): the same Core signal blocks, or does not, by tenant configuration
…uate GAP-020

Two corrections to my own work in this campaign.

1. I said CP-02 was fully closed. It was not: criterion 4 -- "GAP-020 re-evaluated
   in the light of the live catalogue" -- was still open. Now done, with evidence:

   - the tenant-aware persistence/caching of schema references, which GAP-020
     parked in GAP-004 as "blocked upstream", is RESOLVED and not as foreseen.
     `CoreSyncPhaseArtifactProfileSource` syncs from the Core and caches IN
     MEMORY -- deliberately, because `GetProfiles()` is synchronous and sits on a
     governance decision -- carries `canonicalSchema` per artifact keeping
     `core-schema`, `tool-output` and `none` apart, and the tenant overlay
     preserves it. No database copy was needed: the Core is the authority and a
     copy on disk would be a third version to keep in step.
   - "use them to validate what is filled in" stays WITHDRAWN, same reason:
     T-056, the tenant configures content validation and the engine does not
     hardwire it. The live catalogue does not change that; if anything it
     reinforces it, because the tenant can now mark applicability without anyone
     interpreting content on their behalf.

   GAP-020 is not reopened: what it left pending is either done or deliberately
   out of scope.

2. CP-02, CP-04, CP-06 and CP-14 had every criterion checked and their rows still
   said PENDING -- drift I introduced by updating criteria and prose without
   flipping the status. Both surfaces now agree; `check-gap-registry.py` reports
   203 cards / 203 rows coherent, 179 DONE.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs(board): flip the rows whose criteria are all closed, and re-evaluate GAP-020
…it 14

The inventory guard caught it: `core-sdlc-parity` landed and the generated
roster still said 13. The block is generated, so it was regenerated rather than
hand-edited — editing the number by hand is what the guard exists to catch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs(robosoft): regenerate the robot roster — the parity robot makes it 14
@beyondnetPeru
beyondnetPeru merged commit f430fdf into main Aug 3, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant