From ce9a007a9f24c6f2c5b067d5dfd19f901cb43020 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sun, 1 Nov 2026 10:07:28 +0100 Subject: [PATCH 1/6] docs(decisions): add Current Decisions pages for five areas Add docs/decisions with an index and one page each for Artifact CAS, Attestation Process, Authentication, Service Operations, and Chainloop Trace. The pages hold 59 current decisions (ADR-001 to ADR-063, with 4 Non-Confirmed) taken from the 19 specs in docs/specs, each checked against the code, plus three open conflicts. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino Chainloop-Trace-Sessions: e3100522-144f-4450-91f7-eed1cd2c29d9 --- docs/decisions/README.md | 89 +++++++ docs/decisions/artifact-cas.md | 109 ++++++++ docs/decisions/attestation-process.md | 74 ++++++ docs/decisions/authentication.md | 102 ++++++++ docs/decisions/chainloop-trace.md | 353 ++++++++++++++++++++++++++ docs/decisions/service-operations.md | 82 ++++++ 6 files changed, 809 insertions(+) create mode 100644 docs/decisions/README.md create mode 100644 docs/decisions/artifact-cas.md create mode 100644 docs/decisions/attestation-process.md create mode 100644 docs/decisions/authentication.md create mode 100644 docs/decisions/chainloop-trace.md create mode 100644 docs/decisions/service-operations.md diff --git a/docs/decisions/README.md b/docs/decisions/README.md new file mode 100644 index 000000000..a98ce4c16 --- /dev/null +++ b/docs/decisions/README.md @@ -0,0 +1,89 @@ + + +# Current Decisions + +> Written by `eng-adr`. To change a decision, write a spec or run `eng-adr new`. Do not edit this page by hand. + +Last refresh: 2026-11-01, commit 4e150ca7. + +## Areas + +### Artifact CAS +- **Page:** [artifact-cas.md](artifact-cas.md) +- **Owner:** migmartri +- **Purpose:** Content-addressable storage of evidence blobs, with its upload and download paths and its caches. +- **Does not own:** the listeners, ports, TLS, and ingress of the CAS (Service Operations), and the download tokens that the control plane makes (Authentication). +- **Specs:** [issue-3543-cas-existence-cache](../specs/issue-3543-cas-existence-cache.md), [issue-3549-cas-download-browser-cache](../specs/issue-3549-cas-download-browser-cache.md) +- **Code:** `app/artifact-cas/`, `pkg/blobmanager/`, `pkg/cache/casexistence/`, `pkg/middlewares/http/` + +### Attestation Process +- **Page:** [attestation-process.md](attestation-process.md) +- **Owner:** migmartri +- **Purpose:** The attestation lifecycle of the CLI (`init`, `add`, `status`, `push`, `reset`, `verify`), and how it selects its organization and project. +- **Does not own:** AI coding session evidence (Chainloop Trace), and login and tokens (Authentication). +- **Specs:** [issue-3063-attestation-repo-config](../specs/issue-3063-attestation-repo-config.md) +- **Code:** `app/cli/cmd/attestation*.go`, `app/cli/pkg/action/attestation_*.go`, `app/cli/internal/repositoryconfig/`, `pkg/attestation/` + +### Authentication +- **Page:** [authentication.md](authentication.md) +- **Owner:** migmartri +- **Purpose:** User login through the control plane, the login callback, and the hand-off of the session token to the CLI and other clients. +- **Does not own:** the CAS download token header (Artifact CAS), and the organization that a command uses (Attestation Process). +- **Specs:** [issue-3626-login-code-exchange](../specs/issue-3626-login-code-exchange.md) +- **Code:** `app/controlplane/internal/service/auth.go`, `app/controlplane/pkg/biz/loginhandoff.go`, `app/controlplane/pkg/data/loginhandoff.go`, `app/cli/cmd/auth_login.go` + +### Service Operations +- **Page:** [service-operations.md](service-operations.md) +- **Owner:** migmartri +- **Purpose:** The network endpoints of the control plane and the CAS: listeners, protocols, TLS, internal ports, and the Helm chart ingress. +- **Does not own:** what the endpoints serve (Artifact CAS, Authentication). +- **Specs:** [issue-3628-single-endpoint](../specs/issue-3628-single-endpoint.md), [issue-3628-controlplane-http-dispatch](../specs/issue-3628-controlplane-http-dispatch.md) +- **Code:** `app/controlplane/internal/server/`, `app/artifact-cas/internal/server/`, `deployment/chainloop/` + +### Chainloop Trace +- **Page:** [chainloop-trace.md](chainloop-trace.md) +- **Owner:** jiparis +- **Purpose:** Recording AI coding sessions as evidence: providers, spec and skill capture, transcript pointers, line attribution, local mode, and the e2e test matrix. +- **Does not own:** the attestation lifecycle and the organization order of `.chainloop.yml` (Attestation Process), and blob storage (Artifact CAS). +- **Specs:** [issue-3495-session-spec-capture](../specs/issue-3495-session-spec-capture.md), [issue-3515-spec-capture-during-session](../specs/issue-3515-spec-capture-during-session.md), [issue-3520-pi-trace-provider](../specs/issue-3520-pi-trace-provider.md), [issue-3531-spec-source-role](../specs/issue-3531-spec-source-role.md), [issue-3540-trace-skills-used](../specs/issue-3540-trace-skills-used.md), [issue-3556-session-image-placeholders](../specs/issue-3556-session-image-placeholders.md), [issue-3561-up-to-date-local-spec-evidence](../specs/issue-3561-up-to-date-local-spec-evidence.md), [issue-3569-pasted-image-pointers](../specs/issue-3569-pasted-image-pointers.md), [issue-3582-trace-evidence-export](../specs/issue-3582-trace-evidence-export.md), [issue-3635-trace-e2e-matrix](../specs/issue-3635-trace-e2e-matrix.md), [issue-3639-line-ranges](../specs/issue-3639-line-ranges.md), [issue-3639-trace-line-attribution](../specs/issue-3639-trace-line-attribution.md), [issue-3657-codex-trace-provider](../specs/issue-3657-codex-trace-provider.md) +- **Code:** `app/cli/internal/trace/`, `app/cli/cmd/trace*.go`, `app/cli/pkg/action/trace_*.go`, `pkg/attestation/crafter/materials/aicodingsession/` + +## Relationships + +- **Attestation Process → Chainloop Trace:** both read `.chainloop.yml` through one reader (ADR-003, attestation-process.md). A change to the file order or keys changes both areas. +- **Chainloop Trace → Artifact CAS:** Trace uploads each spec source, skill package, and pasted image as its own material. A transcript pointer replaces a copy only when its material is stored (ADR-028, chainloop-trace.md). +- **Service Operations → Artifact CAS, Authentication:** the shared endpoint carries the CAS ByteStream and download routes and the login endpoints. A change to the dispatch or TLS affects all of them. + +## Repository-wide decisions + +None. + +## Non-Confirmed + +None. + +## Superseded + +None. + +## Open conflicts + +None. diff --git a/docs/decisions/artifact-cas.md b/docs/decisions/artifact-cas.md new file mode 100644 index 000000000..31ba33bfd --- /dev/null +++ b/docs/decisions/artifact-cas.md @@ -0,0 +1,109 @@ + + +# Artifact CAS: Current Decisions + +> Written by `eng-adr`. To change a decision, write a spec or run `eng-adr new`. Do not edit this page by hand. + +**Purpose:** Content-addressable storage of evidence blobs, with its upload and download paths and its caches. **Owner:** migmartri. **Does not own:** the listeners, ports, TLS, and ingress of the CAS (Service Operations), and the download tokens that the control plane makes (Authentication). **Specs:** [issue-3543-cas-existence-cache](../specs/issue-3543-cas-existence-cache.md), [issue-3549-cas-download-browser-cache](../specs/issue-3549-cas-download-browser-cache.md). **Last refresh:** 2026-11-01, commit 4e150ca7. + +## Language + +- **Existence cache**: the server-side cache of "this blob is in the backend" results, keyed per organization and backend. _Avoid_: "upload cache", because the cache holds no content. +- **Client cache**: the in-process cache of backend clients and their credentials. _Avoid_: "credentials cache", because the credentials are part of the client and never stored alone. + +## Current decisions + +### ADR-016: Existence cache in NATS KV when connected, in memory otherwise +The existence cache uses the shared cache library. It uses NATS KV when the CAS has a NATS connection, and an in-memory LRU when it does not. +- Why: replicas share hits when NATS is there, and the CAS gets no new dependency when it is not. +- Rejected: memory only (the hit rate drops as replicas grow). NATS required (a hard dependency for a speed gain). +- Deciders: migmartri, javirln +- Source: [issue-3543-cas-existence-cache D-001](../specs/issue-3543-cas-existence-cache.md) +- Code: verified: `pkg/cache/casexistence/casexistence.go`, `app/artifact-cas/cmd/wire.go`. + +### ADR-017: The existence cache lives in the CAS, server side +The CAS ByteStream write checks the cache and answers "already exists" with no backend call. The CLI has no cache. +- Why: it works for all CLI versions and all clients, with no API change. +- Rejected: a cache in the CLI (each command is a new process, so nothing is reused). +- Deciders: migmartri, javirln +- Source: [issue-3543-cas-existence-cache D-003](../specs/issue-3543-cas-existence-cache.md) +- Code: verified: `app/artifact-cas/internal/service/bytestream.go`, `app/artifact-cas/internal/service/cache.go`. + +### ADR-018: The cache key starts with the organization +The key is the organization, then the backend type, then a hash of the secret reference, then the digest. The cache has its own bucket. +- Why: the organization prefix keeps the entries of one tenant away from all other tenants, also on shared storage. The secret reference separates backends in one organization. +- Rejected: the digest alone (the upload of one tenant answers for another tenant, and shows that the other tenant has the file). The backend and the digest without the organization (a shared backend leaks the same information). +- Deciders: migmartri, javirln +- Source: [issue-3543-cas-existence-cache D-004](../specs/issue-3543-cas-existence-cache.md) +- Code: verified: `pkg/cache/casexistence/casexistence.go`. `app/artifact-cas/internal/service/cache.go` skips the cache when the organization is empty. + +### ADR-019: Credentials never go to a shared store +The client cache, which holds backend credentials, stays in process memory. It never uses NATS. +- Why: credentials must not leave the process. +- Deciders: migmartri, javirln +- Source: [issue-3543-cas-existence-cache D-006](../specs/issue-3543-cas-existence-cache.md) +- Code: verified: `app/artifact-cas/internal/service/cache.go` (in-process expirable LRU). + +### ADR-020: Downloads use the browser HTTP cache +The CAS sets cache headers on downloads and answers 304 when the browser has the content. The control plane keeps no copy of the bytes. +- Why: the browser downloads straight from the CAS, so a cache there skips the backend copy and the transfer. +- Rejected: a server cache in the control plane (the control plane never sees the bytes, and its message size limit is too small). +- Deciders: jiparis, migmartri +- Source: [issue-3549-cas-download-browser-cache D-001](../specs/issue-3549-cas-download-browser-cache.md) +- Code: verified: `app/artifact-cas/internal/service/download.go`. + +### ADR-021: The download token goes in the Authorization header +The CAS accepts the download token in an `Authorization: Bearer` header, so the download URL stays the same between calls. +- Why: the browser cache uses the URL as its key, and the token changes on each call. +- Rejected: one token for each user and digest (the URL stays the same only for the life of the token). +- Deciders: jiparis, migmartri +- Source: [issue-3549-cas-download-browser-cache D-003](../specs/issue-3549-cas-download-browser-cache.md) +- Code: verified on the CAS side: `pkg/middlewares/http/jwt.go`, CORS allows `Authorization`. The control plane `GetDownloadURL` still puts the token in `?t=` (`app/controlplane/internal/service/casredirect.go`). The client that moves the token to the header is not in this repository. + +### ADR-022: The header token wins, with no fallback to the query +When a request has a token in the header and in the query, the CAS uses only the header. A bad header fails with 401. +- Why: a client that sends a header sets its intent. +- Rejected: reject a request with both (the viewer must then strip the query). The query wins (the header has no effect for a URL that still holds a token). +- Deciders: jiparis, migmartri +- Source: [issue-3549-cas-download-browser-cache D-004](../specs/issue-3549-cas-download-browser-cache.md) +- Code: verified: `pkg/middlewares/http/jwt.go`. + +### ADR-023: The CAS checks that the object exists before a 304 +Before it answers 304, the CAS makes a metadata call to the backend. A deleted object gives 404. +- Why: a deleted object stops showing at once, and the metadata call is cheap. +- Rejected: a 304 from the digest alone (a browser can show a deleted object until it drops its copy). +- Deciders: jiparis, migmartri +- Source: [issue-3549-cas-download-browser-cache D-005](../specs/issue-3549-cas-download-browser-cache.md) +- Code: verified: `app/artifact-cas/internal/service/download.go`. + +Not shown: 5 smaller decisions of [issue-3543-cas-existence-cache](../specs/issue-3543-cas-existence-cache.md), 2 of [issue-3549-cas-download-browser-cache](../specs/issue-3549-cas-download-browser-cache.md). + +## Non-Confirmed + +None. + +## Superseded + +None. + +## Open conflicts + +None. diff --git a/docs/decisions/attestation-process.md b/docs/decisions/attestation-process.md new file mode 100644 index 000000000..7737c65bb --- /dev/null +++ b/docs/decisions/attestation-process.md @@ -0,0 +1,74 @@ + + +# Attestation Process: Current Decisions + +> Written by `eng-adr`. To change a decision, write a spec or run `eng-adr new`. Do not edit this page by hand. + +**Purpose:** The attestation lifecycle of the CLI (`init`, `add`, `status`, `push`, `reset`, `verify`), and how it selects its organization and project. **Owner:** migmartri. **Does not own:** AI coding session evidence (Chainloop Trace), and login and tokens (Authentication). **Specs:** [issue-3063-attestation-repo-config](../specs/issue-3063-attestation-repo-config.md). **Last refresh:** 2026-11-01, commit 4e150ca7. + +## Language + +- **Repository config**: the `.chainloop.yml` file at the repository root that pins `organization` and `projectName` for the repository. _Avoid_: "workflow file", because the file holds no workflow. +- **Saved default**: the organization that the CLI keeps in its own configuration for the user. _Avoid_: "default org" without a qualifier, because the repository config can also give an organization. + +## Current decisions + +### ADR-001: Organization order is flag, environment variable, repository config, saved default +The `--org` flag and its environment variable win. The repository config applies only when neither is set, and it comes before the saved default. +- Why: the file pins the organization for the repository. +- Rejected: the saved default before the file (most users have a saved default, so the file would almost never apply). +- Deciders: migmartri, javirln +- Source: [issue-3063-attestation-repo-config D-002](../specs/issue-3063-attestation-repo-config.md) +- Code: verified: `app/cli/cmd/attestation.go`, `app/cli/cmd/root.go`. For a command other than init with no remote state, the organization of the local attestation state also comes before the saved default (`attestation.go`). + +### ADR-003: One reader of `.chainloop.yml` for attestation and trace +Attestation commands and Trace read the repository config through one package. +- Why: two readers already disagreed on the file order. +- Rejected: a second change to the attestation reader only. +- Deciders: migmartri, javirln +- Source: [issue-3063-attestation-repo-config D-007](../specs/issue-3063-attestation-repo-config.md) +- Code: verified for reads: `app/cli/internal/repositoryconfig/config.go`, used by `app/cli/cmd/attestation.go` and `app/cli/internal/trace/config/config.go`. Writes still use their own lookup. + +### ADR-004: The repository config organization acts as `--org`, with a check for API tokens +The organization from the file has the same effect as `--org` for the run. It is not saved as the default. When an API token belongs to a different organization, the command stops with an error that names both. +- Why: one rule for users: the file is a `--org` that the repository keeps. +- Rejected: an exact copy of `--org` (with an API token, it ignores a different organization with no message). A warning only (CI logs often hide it). +- Deciders: migmartri, javirln +- Source: [issue-3063-attestation-repo-config D-010](../specs/issue-3063-attestation-repo-config.md) +- Code: verified: `app/cli/cmd/attestation.go`, `app/cli/internal/token/token.go`, `app/cli/cmd/root.go`. + +Not shown: 6 smaller decisions of [issue-3063-attestation-repo-config](../specs/issue-3063-attestation-repo-config.md). + +## Non-Confirmed + +### ADR-002: All attestation commands that contact the control plane read the organization from the file +- Why: with remote state, a command must connect to the correct organization before it can load the state. +- Deciders: migmartri, javirln +- Source: [issue-3063-attestation-repo-config D-004](../specs/issue-3063-attestation-repo-config.md) +- Code: not confirmed: init, add, push, status, and reset read the file (`app/cli/cmd/attestation.go`). `attestation verify` contacts the control plane (`app/cli/pkg/action/attestation_verify.go`) and does not read the file. The parent ticket is open. + +## Superseded + +None. + +## Open conflicts + +None. diff --git a/docs/decisions/authentication.md b/docs/decisions/authentication.md new file mode 100644 index 000000000..2db260f46 --- /dev/null +++ b/docs/decisions/authentication.md @@ -0,0 +1,102 @@ + + +# Authentication: Current Decisions + +> Written by `eng-adr`. To change a decision, write a spec or run `eng-adr new`. Do not edit this page by hand. + +**Purpose:** User login through the control plane, the login callback, and the hand-off of the session token to the CLI and other clients. **Owner:** migmartri. **Does not own:** the CAS download token header (Artifact CAS), and the organization that a command uses (Attestation Process). **Specs:** [issue-3626-login-code-exchange](../specs/issue-3626-login-code-exchange.md). **Last refresh:** 2026-11-01, commit 4e150ca7. + +## Language + +- **Handoff code**: the single-use code that the login callback gets in place of the session token. The client exchanges it for the token. _Avoid_: "auth code" or "code", because a callback can also be an OAuth client that gets its own `code`. +- **Legacy callback**: the callback that carries the session token in `?t=`. _Avoid_: "old login", because headless login is a different flow. + +## Current decisions + +### ADR-037: The callback gets a single-use handoff code, not the token +When the client sends a challenge, the login callback gets a single-use code. The client exchanges the code for the session token. +- Why: it takes the token out of the URL. +- Rejected: keep `?t=` and add checks (the token is still in the URL). The token in the URL fragment (still in history, and fixation stays). A full OAuth server with refresh tokens (a much larger change). +- Deciders: migmartri, jiparis +- Source: [issue-3626-login-code-exchange D-001](../specs/issue-3626-login-code-exchange.md) +- Code: verified: `app/controlplane/internal/service/auth.go`, `app/controlplane/pkg/biz/loginhandoff.go`, `app/cli/cmd/auth_login.go`. + +### ADR-038: The code is bound to an S256 challenge and the callback URL +The client sends a PKCE-style S256 challenge. The exchange checks the verifier and the callback URL. +- Why: it works for the CLI and for a browser app, and needs no cookie at exchange time. +- Rejected: bind to the callback URL only (a web app has a fixed callback, so fixation stays). Require the OIDC state cookie at exchange (the CLI and a cross-origin web app do not have it). +- Deciders: migmartri, jiparis +- Source: [issue-3626-login-code-exchange D-002](../specs/issue-3626-login-code-exchange.md) +- Code: verified: `app/controlplane/pkg/biz/loginhandoff.go` (`Issue`, `Exchange`), `app/cli/cmd/auth_login.go`. + +### ADR-039: The code store is a database table with no token at rest +The table holds the code hash, the user, the expiry, and the challenge. One conditional update consumes the code. The control plane makes the token at exchange time. +- Why: it works with many replicas and enforces single use. +- Rejected: store the token with the code (a token at rest in the database). Memory (fails with more than one replica). A signed code with no store (cannot enforce single use). +- Deciders: migmartri, jiparis +- Source: [issue-3626-login-code-exchange D-004](../specs/issue-3626-login-code-exchange.md) +- Code: verified: `app/controlplane/pkg/data/loginhandoff.go`, `app/controlplane/internal/service/auth.go`. + +### ADR-040: Opt-in per request, with flags on the info endpoint +The server issues a code only when the request has a challenge. The info endpoint reports whether the exchange is supported and whether the legacy callback is allowed. +- Why: old CLIs need no change. A new CLI knows what to expect and can detect a downgrade. +- Rejected: opt-in with no flag (the CLI cannot tell a downgrade from an old server). A version check (fails with forks and development builds). +- Deciders: migmartri, jiparis +- Source: [issue-3626-login-code-exchange D-005](../specs/issue-3626-login-code-exchange.md) +- Code: verified: `app/controlplane/api/controlplane/v1/status.proto`, `app/controlplane/internal/service/status.go`, `app/cli/cmd/auth_login.go`. + +### ADR-041: The CLI accepts `?t=` only while the server allows the legacy callback +When the CLI sent a challenge and gets `?t=`, it accepts the token with a warning while the server reports legacy allowed. It refuses the token when legacy is denied. +- Why: a login page that does not forward the new parameters yet must not break login. The loopback listener has a random port and a short life, so the fixation risk is low. +- Rejected: refuse `?t=` from the first CLI release (it breaks login behind such a login page). +- Deciders: migmartri, jiparis +- Source: [issue-3626-login-code-exchange D-007](../specs/issue-3626-login-code-exchange.md) +- Code: verified: `app/cli/cmd/auth_login.go`. + +### ADR-042: One error to the client for each exchange failure +Each exchange failure gives the client the same error. The specific reason goes to the server log only. +- Why: a uniform client error gives no hint to a person who guesses codes. The server log reason helps operators debug failed logins. +- Rejected: a uniform error with no server log reason (hard to debug). +- Deciders: migmartri, jiparis +- Source: [issue-3626-login-code-exchange D-012](../specs/issue-3626-login-code-exchange.md) +- Code: verified: `app/controlplane/internal/service/auth.go`. + +### ADR-043: The exchange endpoint is gRPC only +The exchange is an unauthenticated RPC on the auth service, with no HTTP mapping. +- Why: the CLI uses gRPC and the web UI uses gRPC-Web, so no client needs a plain HTTP route. With no HTTP route, the public surface of the unauthenticated endpoint is smaller, and the web UI makes no cross-origin preflight. +- Rejected: keep an HTTP mapping for a future non-gRPC client (no such client exists). +- Deciders: migmartri, matiasinsaurralde +- Source: [issue-3626-login-code-exchange D-013](../specs/issue-3626-login-code-exchange.md) +- Code: verified: `app/controlplane/api/controlplane/v1/auth.proto` has no `google.api.http` option. `app/controlplane/internal/server/grpc.go`. + +Not shown: 6 smaller decisions of [issue-3626-login-code-exchange](../specs/issue-3626-login-code-exchange.md). + +## Non-Confirmed + +None. + +## Superseded + +None. + +## Open conflicts + +None. diff --git a/docs/decisions/chainloop-trace.md b/docs/decisions/chainloop-trace.md new file mode 100644 index 000000000..ba175bc96 --- /dev/null +++ b/docs/decisions/chainloop-trace.md @@ -0,0 +1,353 @@ + + +# Chainloop Trace: Current Decisions + +> Written by `eng-adr`. To change a decision, write a spec or run `eng-adr new`. Do not edit this page by hand. + +**Purpose:** Recording AI coding sessions as evidence: providers, spec and skill capture, transcript pointers, line attribution, local mode, and the e2e test matrix. **Owner:** jiparis. **Does not own:** the attestation lifecycle and the organization order of `.chainloop.yml` (Attestation Process), and blob storage (Artifact CAS). **Specs:** [issue-3495-session-spec-capture](../specs/issue-3495-session-spec-capture.md), [issue-3515-spec-capture-during-session](../specs/issue-3515-spec-capture-during-session.md), [issue-3520-pi-trace-provider](../specs/issue-3520-pi-trace-provider.md), [issue-3531-spec-source-role](../specs/issue-3531-spec-source-role.md), [issue-3540-trace-skills-used](../specs/issue-3540-trace-skills-used.md), [issue-3556-session-image-placeholders](../specs/issue-3556-session-image-placeholders.md), [issue-3561-up-to-date-local-spec-evidence](../specs/issue-3561-up-to-date-local-spec-evidence.md), [issue-3569-pasted-image-pointers](../specs/issue-3569-pasted-image-pointers.md), [issue-3582-trace-evidence-export](../specs/issue-3582-trace-evidence-export.md), [issue-3635-trace-e2e-matrix](../specs/issue-3635-trace-e2e-matrix.md), [issue-3639-line-ranges](../specs/issue-3639-line-ranges.md), [issue-3639-trace-line-attribution](../specs/issue-3639-trace-line-attribution.md), [issue-3657-codex-trace-provider](../specs/issue-3657-codex-trace-provider.md). **Last refresh:** 2026-11-01, commit 4e150ca7. + +## Language + +- **Provider**: the Trace integration for one AI coding agent (Claude Code, Cursor, OpenCode, Pi, Codex). _Avoid_: "harness" in code and evidence. The e2e suite uses "harness" for the agent CLI that it runs. +- **Spec source**: an input that the session was built from (a ticket, a document, a plan, an image, a skill). It is recorded as a spec entry and its own material. _Avoid_: "attachment", because a source is evidence with a digest, not a file on a message. +- **Capture**: a file that the agent writes into the spec folder for one source. _Avoid_: "export", because the agent writes the capture, not the CLI. +- **Pointer**: the `chainloop.replaced` object that takes the place of a copy of a spec source in the transcript. _Avoid_: "placeholder", which is the fixed line in a local-file capture. +- **Local mode**: the storage mode (`traceStorage: LOCAL`) that stores sessions on disk with no control plane. _Avoid_: "offline", because the agent and the model are still online. Also avoid "export mode". +- **Net contribution**: the lines that a session added and that no later commit replaced. _Avoid_: "authored lines", which counts lines that later commits replaced. + +## Current decisions + +### ADR-005: The agent decides what the spec is +An instruction at session start tells the agent to resolve the spec of the session and write it into the spec folder. +- Why: only the agent knows which input sets the task, and it reaches sources that the transcript never holds. +- Rejected: extraction from the transcript by the CLI. It needs rules to pick the spec, misses sources that the agent never opened, and needs one extractor for each agent. Extraction on the server after ingestion (the spec is not in the attestation). +- Deciders: jiparis, migmartri +- Source: [issue-3495-session-spec-capture D-001](../specs/issue-3495-session-spec-capture.md) +- Code: verified: `app/cli/pkg/action/trace_spec.go`, `app/cli/pkg/action/trace_agent_hook.go`. +- Partly replaced by ADR-013: the CLI makes the skill entries. + +### ADR-006: A capture is Markdown with a short header +A capture is a Markdown body under a frontmatter header (kind, uri, role, title, description). With no header or a bad header, the whole file is content. An unknown kind becomes `text`. +- Why: a model writes long Markdown reliably. Long text escaped inside JSON fails completely when one character is wrong. A bad header still keeps the content. +- Rejected: JSON files. +- Deciders: jiparis, migmartri +- Source: [issue-3495-session-spec-capture D-003](../specs/issue-3495-session-spec-capture.md) +- Code: verified: `app/cli/internal/trace/spec/parse.go`. + +### ADR-007: The agent does not capture a pasted image +The instructions tell the agent not to capture an image pasted into the conversation, also when it has a file path for it. +- Why: the agent cannot copy the bytes of a pasted image, and a description that the agent writes is not the image. +- Rejected: a description of kind `image` (the earlier rule of issue-3495 D-010). +- Deciders: migmartri, jiparis +- Source: [issue-3515-spec-capture-during-session D-009](../specs/issue-3515-spec-capture-during-session.md) +- Code: verified: `app/cli/pkg/action/trace_spec.go`, `app/cli/internal/trace/README.md`. See Open conflicts. + +### ADR-008: Pi integration is a project-local TypeScript extension +`trace init` installs a marked extension at `.pi/extensions/chainloop-trace.ts`, which sends Pi lifecycle and tool events to the Trace hooks. +- Why: Pi documents lifecycle and tool events for extensions. +- Rejected: a Pi fork, or a file watcher. +- Deciders: waveywaves, migmartri, jiparis +- Source: [issue-3520-pi-trace-provider D-001](../specs/issue-3520-pi-trace-provider.md) +- Code: verified: `app/cli/internal/trace/pi/hooks.go`. + +### ADR-009: The Pi provider parses a copy of the Pi JSONL v3 transcript +The provider keeps a private copy of the Pi session file and parses only that copy. +- Why: the Pi transcript is the authoritative record of provider, model, usage, tool, and conversation data. +- Rejected: a duplicate transcript that the extension writes. +- Deciders: waveywaves, migmartri, jiparis +- Source: [issue-3520-pi-trace-provider D-006](../specs/issue-3520-pi-trace-provider.md) +- Code: verified: `app/cli/internal/trace/pi/provider.go`, `app/cli/internal/trace/pi/parse.go`. + +### ADR-010: File snapshot keys include the tool call ID +The hook input field `tool_use_id` is part of the file snapshot key when the provider sends it. Without it, the key is the session and the path. +- Why: it prevents an overwrite between concurrent tool calls, without a second name for one value and without the subagent identity. +- Deciders: waveywaves, migmartri, jiparis +- Source: [issue-3520-pi-trace-provider D-012](../specs/issue-3520-pi-trace-provider.md) +- Code: verified: `app/cli/internal/trace/state/snapshot.go`. Pi, Claude Code, and Codex send it. The OpenCode key has no tool call ID (`app/cli/internal/trace/opencode/provider.go`). + +### ADR-011: A binary source gets its role from a companion file +For a binary file, the agent writes `.meta.yaml` next to it, with the role, the title, and the description. +- Why: the agent decides the role of every source, also of an image. +- Rejected: the CLI sets `reference` for each image (a guess, and a mockup can be the spec). No role for binary files. +- Deciders: jiparis, migmartri +- Source: [issue-3531-spec-source-role D-003](../specs/issue-3531-spec-source-role.md) +- Code: verified: `app/cli/internal/trace/spec/spec.go`, `app/cli/pkg/action/trace_spec_materials.go`. + +### ADR-012: A used skill is a spec entry of kind `skill` +Each skill that the session used is an entry of kind `skill` in the spec list of the session. +- Why: a skill is a source of the session, like a ticket or a plan. It gets the same storage, redaction, and annotations. +- Rejected: a separate `skills_used` list in the session material. +- Deciders: jiparis, migmartri +- Source: [issue-3540-trace-skills-used D-001](../specs/issue-3540-trace-skills-used.md) +- Code: verified: `pkg/attestation/crafter/materials/aicodingsession/aicodingsession.go`, `app/cli/pkg/action/trace_skills.go`. + +### ADR-013: The CLI makes the skill entries from the transcript +The CLI finds skill use in the transcript and the hook events. The agent does not capture skills. +- Why: skill use is a fact in the transcript, and no rule must pick it. The reason of ADR-005 (the CLI cannot tell which input sets the task) does not apply to skills. +- Deciders: jiparis, migmartri +- Source: [issue-3540-trace-skills-used D-002](../specs/issue-3540-trace-skills-used.md) +- Code: verified for Claude Code, Codex, and OpenCode: `app/cli/internal/trace/claude/skills.go`, `app/cli/pkg/action/trace_skills.go`. Cursor and Pi have no skill tracking. + +### ADR-014: The whole skill folder is recorded +The CLI copies the full folder of a used skill, not only the text that the agent loaded. +- Why: a skill can run its own scripts, and a reviewer needs that code. The attestation must not depend on the `ai-agent-config` material. +- Rejected: the `SKILL.md` text from the transcript (no scripts, and Claude Code deletes the frontmatter). +- Deciders: jiparis, migmartri +- Source: [issue-3540-trace-skills-used D-004](../specs/issue-3540-trace-skills-used.md) +- Code: verified: `app/cli/pkg/action/trace_skills.go`, `app/cli/internal/trace/skill/skill.go`. + +### ADR-015: One reproducible archive for each skill +Each skill gives one archive with fixed file times, stored as one package material. +- Why: one package digest for each skill, and the same skill version gives the same digest in all sessions. +- Rejected: one material for each file (it fills the attestation for a skill with many files). +- Deciders: jiparis, migmartri +- Source: [issue-3540-trace-skills-used D-006](../specs/issue-3540-trace-skills-used.md) +- Code: verified: `app/cli/internal/trace/skill/skill.go`, `app/cli/pkg/action/trace_skills.go`. + +### ADR-024: Copies of a source become digest pointers in the transcript +The content stays in the spec material. Each copy in the transcript is replaced in place by a pointer with the digest. +- Why: the transcript gets small and keeps the position of each copy. The content stays in the evidence. +- Rejected: drop the copies (a reader loses the position). Delete only the second copy of read files (the transcript is still large, and the redaction still scans the bytes). +- Deciders: migmartri, javirln +- Source: [issue-3556-session-image-placeholders D-001](../specs/issue-3556-session-image-placeholders.md) +- Code: verified for Claude Code and Codex: `app/cli/internal/trace/pointer/pointer.go`, `app/cli/internal/trace/claude/speccopies.go`. Cursor, OpenCode, and Pi have no replacer. + +### ADR-025: The CLI replaces copies at push time, before redaction +The replacement runs in the CLI at push time, before the CLI records and redacts the session material. +- Why: only the CLI can read the file on disk for the match, and the redaction then never scans the replaced bytes. +- Rejected: in the control plane after the upload (the bytes are already uploaded and scanned, and the file is not on disk). +- Deciders: migmartri, javirln +- Source: [issue-3556-session-image-placeholders D-002](../specs/issue-3556-session-image-placeholders.md) +- Code: verified: `app/cli/pkg/action/trace_hook_handler.go`. + +### ADR-026: A pointer replaces the content in place and keeps the block type +The pointer takes the place of the content inside the same transcript block, which keeps its type and position. +- Why: a transcript view finds each copy where it was, with no join to other data. +- Rejected: a separate list of replacements in the session material (the view must join the list to the transcript). +- Deciders: migmartri, javirln +- Source: [issue-3556-session-image-placeholders D-003](../specs/issue-3556-session-image-placeholders.md) +- Code: verified: `app/cli/internal/trace/claude/pastedimages.go`, `app/cli/internal/trace/pointer/pointer.go`. + +### ADR-027: One finder for each kind of copy, with optional pointer fields +Each kind of copy (file read, file write, pasted image) has its own finder, and all use the same pointer. A later kind adds optional fields. +- Why: other inputs can use the same mechanism later. +- Rejected: an image-only pointer (each new kind needs a new format and a new view). A version and a kind field in each pointer (optional fields are enough, and the material holds the kind). +- Deciders: migmartri, javirln +- Source: [issue-3556-session-image-placeholders D-008](../specs/issue-3556-session-image-placeholders.md) +- Code: verified: `app/cli/internal/trace/pointer/pointer.go`. + +### ADR-028: Replace a copy only on an exact digest match +The CLI replaces a copy only when the material is stored and its digest equals the digest of the copy. +- Why: the transcript is evidence, and a wrong pointer loses content with no way back. +- Rejected: a best-effort match, for example by size or file name (a wrong match deletes content that is stored nowhere else). +- Deciders: migmartri, javirln +- Source: [issue-3556-session-image-placeholders D-010](../specs/issue-3556-session-image-placeholders.md) +- Code: verified: `app/cli/internal/trace/pointer/pointer.go`, `app/cli/internal/trace/claude/speccopies.go`. + +### ADR-029: Each exact copy of a spec source is replaced, for all file types +The replacement covers each exact copy of a source in the spec list, from file reads and file writes, for all file types. Other media stay inline. +- Why: one rule covers images and text sources. Each pointer resolves in the session itself, and the CLI never decides on its own that content is evidence. +- Rejected: images only (the write copies of tickets stay, and the redaction still scans them). Upload each other media block too (the agent did not choose it). +- Deciders: migmartri, javirln +- Source: [issue-3556-session-image-placeholders D-013](../specs/issue-3556-session-image-placeholders.md) +- Code: verified: `app/cli/internal/trace/claude/speccopies.go`, `app/cli/pkg/action/trace_spec_materials.go`. + +### ADR-030: The CLI captures pasted images at push time +At push time, the CLI makes an image spec entry for each pasted image in the transcript, then replaces its inline copy with a pointer. +- Why: the bytes are in the transcript, so the capture depends on neither the agent nor a file on disk. +- Rejected: replace only the images that the agent captured. Images then stay inline when the agent does not copy them, and the agent can copy only from a file path. +- Deciders: migmartri, matiasinsaurralde +- Source: [issue-3569-pasted-image-pointers D-001](../specs/issue-3569-pasted-image-pointers.md) +- Code: verified for Claude Code and Codex: `app/cli/pkg/action/trace_pasted_images.go`, `app/cli/pkg/action/trace_hook_handler.go`, `app/cli/internal/trace/codex/materials.go`. + +### ADR-032: Pasted image bytes come from the transcript image block +The CLI decodes the image data from the transcript block that the model got. +- Why: the data is what the model got, and it is always there. +- Rejected: read the clipboard file that the agent names. It is a temporary file that can be gone at push time, and its format is specific to one agent. +- Deciders: migmartri, matiasinsaurralde +- Source: [issue-3569-pasted-image-pointers D-005](../specs/issue-3569-pasted-image-pointers.md) +- Code: verified: `app/cli/internal/trace/provider.go`, `app/cli/internal/trace/claude/pastedimages.go`, `app/cli/internal/trace/codex/materials.go`. + +### ADR-033: Local mode writes an evidence set, not a signed attestation +Local mode writes the evidence record and its materials, with no envelope and no signature. +- Why: a developer and a test need the evidence, not a signature. +- Rejected: a signed bundle with no control plane (it needs a contract and a signing key, a larger and separate effort). +- Deciders: matiasinsaurralde, migmartri +- Source: [issue-3582-trace-evidence-export D-002](../specs/issue-3582-trace-evidence-export.md) +- Code: verified: `app/cli/pkg/action/trace_local_store.go`. + +### ADR-034: `trace init --local` needs no control plane +`trace init --local` sets up local mode with no login and no workflow, and writes `traceStorage: LOCAL`. +- Why: without it, init needs a login and creates a workflow on the control plane, so a developer cannot use Trace without a backend. +- Rejected: a run-only flag with no local init (init still needs the control plane). +- Deciders: matiasinsaurralde, migmartri +- Source: [issue-3582-trace-evidence-export D-009](../specs/issue-3582-trace-evidence-export.md) +- Code: verified: `app/cli/cmd/trace_init.go`. + +### ADR-035: In local mode, the pre-push hook stores the session +When the storage mode is local, the pre-push hook stores the sessions on disk and contacts no control plane. +- Why: the normal flow already runs at push time, so storage there needs no new wrapper. +- Rejected: `trace run --export` as the surface (a single-run command, not the normal push-time path). +- Deciders: matiasinsaurralde, migmartri +- Source: [issue-3582-trace-evidence-export D-017](../specs/issue-3582-trace-evidence-export.md) +- Code: verified: `app/cli/cmd/trace_hook.go`, `app/cli/pkg/action/trace_hook_handler.go`. + +### ADR-036: The index of a stored session is an unsigned in-toto statement +A stored session has `statement.json`, rendered by the same Chainloop renderer as a push, with no envelope. +- Why: readers and policies that read a Chainloop statement also read a stored session, and the missing envelope shows that it is unsigned. +- Rejected: a custom `manifest.json` (a second format that only Trace reads). +- Deciders: matiasinsaurralde, migmartri +- Source: [issue-3582-trace-evidence-export D-021](../specs/issue-3582-trace-evidence-export.md) +- Code: verified: `app/cli/pkg/action/trace_local_store.go`. + +### ADR-049: The e2e suite runs the real agent CLI, headless, with a real model +- Why: only a real run shows whether the agent fires the hooks, shows the message, and follows the instruction. +- Rejected: replay recorded hook events into Trace (cheap and stable, but blind to the agent and the model). Both tiers (the replay tier adds little over the unit tests). +- Deciders: jiparis, javirln, migmartri +- Source: [issue-3635-trace-e2e-matrix D-001](../specs/issue-3635-trace-e2e-matrix.md) +- Code: verified: `app/cli/internal/trace/e2e/claude.go`, `app/cli/internal/trace/e2e/codex.go`. + +### ADR-050: The README feature table is the expected matrix +The e2e suite reads the feature tables in the Trace README as its expected results. It keeps no second list. +- Why: one place to keep up to date, and the table must already change in the same PR as a provider change. +- Rejected: a separate data file that generates the table (two formats, and more tooling). No expected matrix (a feature that never worked gives no signal). +- Deciders: jiparis, javirln, migmartri +- Source: [issue-3635-trace-e2e-matrix D-002](../specs/issue-3635-trace-e2e-matrix.md) +- Code: verified: `app/cli/internal/trace/e2e/expected.go`. + +### ADR-051: The e2e suite uses local mode only +- Why: no test organization, no long-lived token in a public repository, and no dependency on a running service. +- Rejected: local mode plus a test organization (a secret and a dependency). A control plane started in the job (slow, and more to maintain). +- Deciders: jiparis, javirln, migmartri +- Source: [issue-3635-trace-e2e-matrix D-007](../specs/issue-3635-trace-e2e-matrix.md) +- Code: verified: `app/cli/internal/trace/e2e/workspace.go`, `app/cli/internal/trace/e2e/runner.go`. + +### ADR-053: Agent versions are pinned and bumped by hand +The suite configuration pins one version for each agent. A maintainer bumps a pin on purpose. +- Why: the job has access to secrets and must not run an agent release that nobody selected. +- Rejected: the latest release on each run (runs code that nobody selected in a job with secrets). Automated bump pull requests (more tooling for the first version). +- Deciders: jiparis, javirln, migmartri +- Source: [issue-3635-trace-e2e-matrix D-013](../specs/issue-3635-trace-e2e-matrix.md) +- Code: verified: `app/cli/internal/trace/e2e/config.yaml`. The suite does not install the agent. It warns when the installed version is not the pinned one. + +### ADR-054: Each line range gets an attribution in the existing list +The ranges stay in the one list of each file entry, and each range gets an attribution, AI or human. +- Why: one list keeps the evidence small and gives readers one place to look. The new property is additive. +- Rejected: a new list of AI ranges next to the old one (two lists for the same lines). Two new lists for AI and human (a reader must merge them to show a file). +- Deciders: jiparis, migmartri +- Source: [issue-3639-line-ranges D-001](../specs/issue-3639-line-ranges.md) +- Code: verified: `pkg/attestation/crafter/materials/aicodingsession/aicodingsession.go`, `app/cli/internal/trace/attribution/enrich.go`. + +### ADR-055: Range positions are in the pushed commit +- Why: the lines that later commits replaced are gone at that commit, so the ranges agree with the net counts. A reader of the pull request sees the same version. +- Rejected: the last commit of the session (a later commit can move or replace the lines). Ranges for each commit (a reader must combine them). +- Deciders: jiparis, migmartri +- Source: [issue-3639-line-ranges D-002](../specs/issue-3639-line-ranges.md) +- Code: verified: `app/cli/internal/trace/attribution/enrich.go`. + +### ADR-056: A range with no attribution is an AI range +Evidence from an older CLI has ranges with no attribution. Readers treat these as AI ranges at edit time. +- Why: platforms that store evidence from older CLIs must read both forms. +- Deciders: jiparis, migmartri +- Source: [issue-3639-line-ranges D-004](../specs/issue-3639-line-ranges.md) +- Code: verified as a documented contract: `pkg/attestation/crafter/materials/aicodingsession/aicodingsession.go`. No reader is in this repository. + +### ADR-057: The reported split is the net contribution +The AI and human numbers for each file and session are the lines that the session added and that no later commit replaced. +- Why: it gives the expected split, and no line counts twice across commits. +- Rejected: authored lines for each commit, with no net (a replaced AI line still counts as AI). +- Deciders: jiparis, migmartri +- Source: [issue-3639-trace-line-attribution D-001](../specs/issue-3639-trace-line-attribution.md) +- Code: verified: `app/cli/internal/trace/attribution/enrich.go`. + +### ADR-058: A replacement sends the owning session again +When a later commit replaces lines of a session, the CLI sends the evidence of that session again. +- Why: the platform already replaces session data with newer evidence, so it needs no new concept. +- Rejected: separate evidence with no session for human commits (a new concept on the platform). No tracking of commits outside sessions (the human work stays uncounted). +- Deciders: jiparis, migmartri +- Source: [issue-3639-trace-line-attribution D-002](../specs/issue-3639-trace-line-attribution.md) +- Code: verified: `app/cli/pkg/action/trace_replacements.go`, `app/cli/pkg/action/trace_hook_handler.go`. + +### ADR-059: Line owners are rebuilt from blame and the ledger +The line fingerprints go into the existing AI line ledger. At push time, the CLI rebuilds the owner of each line from git blame, the commit records, and the ledger. It stores no owner data for each line. +- Why: it reuses the existing storage and pushes nothing to the remote. No second store can disagree with it. +- Rejected: git notes on a dedicated ref (push traffic, ref contention, and git configuration). An owner map for each commit (it duplicates the ledger and needs its own rewrite handling). +- Deciders: jiparis, migmartri +- Source: [issue-3639-trace-line-attribution D-003](../specs/issue-3639-trace-line-attribution.md) +- Code: verified: `app/cli/internal/trace/state/ailines.go`, `app/cli/internal/trace/git/lines.go`. + +### ADR-060: Two categories, human by default +A line that the CLI cannot link to an observed agent edit is human. +- Why: no change to the evidence categories or the platform aggregates. +- Rejected: a third "unknown" category (more exact, but it changes the schema and every aggregate). +- Deciders: jiparis, migmartri +- Source: [issue-3639-trace-line-attribution D-005](../specs/issue-3639-trace-line-attribution.md) +- Code: verified: `app/cli/internal/trace/attribution/enrich.go`. + +### ADR-061: A human commit with no session joins the sessions it changes +At push time, a commit with no session joins each session whose lines it replaced. Only the replacing hunks count, as human lines. +- Why: the human work gets credit where it changed the code of the session, and the evidence needs no new field. +- Rejected: a new field for the replaced lines of the session (the human lines are in no session total). Count the whole commit in the session (unrelated human work joins the session). +- Deciders: jiparis, migmartri +- Source: [issue-3639-trace-line-attribution D-009](../specs/issue-3639-trace-line-attribution.md) +- Code: verified: `app/cli/pkg/action/trace_replacements.go`, `app/cli/internal/trace/attribution/enrich.go`. + +### ADR-062: Codex uses its native hooks +Native Codex hooks connect Codex to the session lifecycle: session start and end, prompts, patches, and shell tools. +- Why: they cover the lifecycle with no separate extension. +- Rejected: a notification-only integration (misses the state before an edit). A separate extension (duplicates lifecycle support). +- Deciders: jiparis, migmartri, matiasinsaurralde +- Source: [issue-3657-codex-trace-provider D-001](../specs/issue-3657-codex-trace-provider.md) +- Code: verified: `app/cli/internal/trace/codex/hooks.go`. + +### ADR-063: The Codex provider parses a copy of the rollout +The provider keeps a private copy of the native rollout, parses only that copy, and estimates cost from embedded prices. +- Why: the evidence stays available after Codex moves or deletes its original transcript. +- Rejected: live transcript parsing (depends on agent storage during assembly). Notification payloads (no conversation and usage details). +- Deciders: jiparis, migmartri, matiasinsaurralde +- Source: [issue-3657-codex-trace-provider D-003](../specs/issue-3657-codex-trace-provider.md) +- Code: verified: `app/cli/internal/trace/codex/copy.go`, `app/cli/internal/trace/codex/parse.go`, `app/cli/internal/trace/codex/pricing.go`. + +Not shown: 10 smaller decisions of [issue-3495-session-spec-capture](../specs/issue-3495-session-spec-capture.md), 8 of [issue-3515-spec-capture-during-session](../specs/issue-3515-spec-capture-during-session.md), 12 of [issue-3520-pi-trace-provider](../specs/issue-3520-pi-trace-provider.md), 10 of [issue-3531-spec-source-role](../specs/issue-3531-spec-source-role.md), 12 of [issue-3540-trace-skills-used](../specs/issue-3540-trace-skills-used.md), 10 of [issue-3556-session-image-placeholders](../specs/issue-3556-session-image-placeholders.md), 10 of [issue-3561-up-to-date-local-spec-evidence](../specs/issue-3561-up-to-date-local-spec-evidence.md), 5 of [issue-3569-pasted-image-pointers](../specs/issue-3569-pasted-image-pointers.md), 20 of [issue-3582-trace-evidence-export](../specs/issue-3582-trace-evidence-export.md), 8 of [issue-3635-trace-e2e-matrix](../specs/issue-3635-trace-e2e-matrix.md), 3 of [issue-3639-line-ranges](../specs/issue-3639-line-ranges.md), 8 of [issue-3639-trace-line-attribution](../specs/issue-3639-trace-line-attribution.md), 2 of [issue-3657-codex-trace-provider](../specs/issue-3657-codex-trace-provider.md). + +## Non-Confirmed + +### ADR-031: The agent keeps capturing pasted images, and its entry wins +The capture instruction for pasted images stays. When the agent and the CLI capture the same image, one entry is kept for the digest, and the entry of the agent wins. +- Why: the agent adds a role, a title, and a description that the CLI cannot know. +- Deciders: migmartri, matiasinsaurralde +- Source: [issue-3569-pasted-image-pointers D-004](../specs/issue-3569-pasted-image-pointers.md) +- Code: not confirmed: the digest rule is built (`app/cli/pkg/action/trace_pasted_images.go`), but the capture instruction is not. See Open conflicts. The parent ticket is done. + +### ADR-052: The e2e suite runs nightly on main and by hand, not on pull requests +- Why: secrets are not available to pull requests from forks, and the cost of a run is high. +- Deciders: jiparis, javirln, migmartri +- Source: [issue-3635-trace-e2e-matrix D-011](../specs/issue-3635-trace-e2e-matrix.md) +- Code: not confirmed: no workflow in `.github/workflows` runs the suite. It runs only by hand with `make test-trace-e2e` (`app/cli/Makefile`). The parent ticket is done. + +## Superseded + +None. + +## Open conflicts + +- ADR-007 ([issue-3515-spec-capture-during-session D-009](../specs/issue-3515-spec-capture-during-session.md)) and ADR-031 ([issue-3569-pasted-image-pointers D-004](../specs/issue-3569-pasted-image-pointers.md)) disagree about the agent capture of pasted images, and neither has a "replaces" note. The instructions follow ADR-007 (`app/cli/pkg/action/trace_spec.go`). The CLI captures pasted images itself for Claude Code and Codex (ADR-030). For Cursor, OpenCode, and Pi, nothing records a pasted image as a source. Proposed: write a spec change that records the current rule. +- ADR-052: the parent ticket is done, but the nightly job does not exist. Proposed: write a spec change that drops the nightly run, so the suite stays manual. diff --git a/docs/decisions/service-operations.md b/docs/decisions/service-operations.md new file mode 100644 index 000000000..fbedafebb --- /dev/null +++ b/docs/decisions/service-operations.md @@ -0,0 +1,82 @@ + + +# Service Operations: Current Decisions + +> Written by `eng-adr`. To change a decision, write a spec or run `eng-adr new`. Do not edit this page by hand. + +**Purpose:** The network endpoints of the control plane and the CAS: listeners, protocols, TLS, internal ports, and the Helm chart ingress. **Owner:** migmartri. **Does not own:** what the endpoints serve (Artifact CAS, Authentication). **Specs:** [issue-3628-single-endpoint](../specs/issue-3628-single-endpoint.md), [issue-3628-controlplane-http-dispatch](../specs/issue-3628-controlplane-http-dispatch.md). **Last refresh:** 2026-11-01, commit 4e150ca7. + +## Language + +- **Shared endpoint**: the HTTP port of a service (`server.http.addr`). It serves native gRPC, plain HTTP, and, on the control plane, gRPC-Web. _Avoid_: "single port" for the legacy listener, and "gRPC port" for the shared endpoint. +- **Legacy gRPC listener**: the separate native gRPC port that stays during the deprecation period. _Avoid_: "the gRPC endpoint", because the shared endpoint also serves gRPC. + +## Current decisions + +### ADR-045: Metrics and profiler ports stay separate +The metrics and profiler endpoints keep their own ports. They are not on the shared endpoint. +- Why: they are internal and must not be on the public hostname. +- Deciders: migmartri, matiasinsaurralde +- Source: [issue-3628-single-endpoint D-006](../specs/issue-3628-single-endpoint.md) +- Code: verified: `app/artifact-cas/internal/server/httpmetrics.go` (separate metrics server), `app/controlplane/internal/server/profiler.go` (pprof on its own port). + +### ADR-046: TLS covers all protocols on the shared endpoint +When the operator configures TLS, all protocols on the shared endpoint use it, and an HTTP ingress must use an HTTPS backend. The legacy gRPC listener keeps its own TLS settings during the deprecation period. +- Why: one certificate and one setting for each service. +- Rejected: plain text and TLS on the same port (a second classification step, and a weaker TLS setting). +- Deciders: migmartri, matiasinsaurralde +- Source: [issue-3628-single-endpoint D-008](../specs/issue-3628-single-endpoint.md) +- Code: verified: `app/artifact-cas/internal/server/http.go`. The legacy listener keeps `server.grpc.tls_config` (`deployment/chainloop/templates/cas/configmap.yaml`). + +### ADR-047: The current HTTP port is the shared endpoint +The HTTP port of each service becomes the shared endpoint. The gRPC port is the legacy listener. +- Why: the human-facing hostname, with the status, download, and login endpoints, is already on the HTTP port. Clients that use ingress hostnames do not break in either direction. +- Rejected: the gRPC port (TLS settings stay in place, but the human-facing hostname stays on a second port). A new port (every client changes). +- Deciders: migmartri, matiasinsaurralde +- Source: [issue-3628-single-endpoint D-012](../specs/issue-3628-single-endpoint.md) +- Code: verified: `app/artifact-cas/internal/server/http.go`, `app/artifact-cas/internal/server/grpc.go` (legacy listener on by default). + +### ADR-048: The server process does the multiplexing, with no proxy or sidecar +Each service serves all protocols on its shared endpoint in its own process. It runs on a VM, in Docker Compose, and in Kubernetes with no proxy or sidecar. +- Why: no extra process, configuration, or lifecycle in any deployment. +- Rejected: a chart-level proxy or Gateway API routes (extra infrastructure, and two ports stay). A mandatory sidecar or embedded proxy (a process and its lifecycle in every deployment). +- Deciders: migmartri, jiparis +- Source: [issue-3628-single-endpoint D-015](../specs/issue-3628-single-endpoint.md) +- Code: verified: `app/controlplane/internal/server/http.go`, `app/artifact-cas/internal/server/http.go`. +- Partly replaced: the spec decision also kept native gRPC and plain HTTP on separate upstream connections. [issue-3628-controlplane-http-dispatch D-001](../specs/issue-3628-controlplane-http-dispatch.md) and [issue-3628-single-endpoint D-020](../specs/issue-3628-single-endpoint.md) replaced that part. + +Not shown: 15 smaller or replaced decisions of [issue-3628-single-endpoint](../specs/issue-3628-single-endpoint.md), 2 of [issue-3628-controlplane-http-dispatch](../specs/issue-3628-controlplane-http-dispatch.md). + +## Non-Confirmed + +### ADR-044: The CAS keeps its native gRPC transport and separate endpoints +- Why: a paused-reader probe showed that the HTTP adapter can read ahead faster than the application receives. +- Deciders: migmartri, jiparis +- Source: [issue-3628-controlplane-http-dispatch D-003](../specs/issue-3628-controlplane-http-dispatch.md) +- Code: not confirmed: the CAS has a shared endpoint (`app/artifact-cas/internal/server/http.go`, `app/artifact-cas/internal/server/grpc.go`). See Open conflicts. The parent ticket is open. + +## Superseded + +None. + +## Open conflicts + +- ADR-044 ([issue-3628-controlplane-http-dispatch D-003](../specs/issue-3628-controlplane-http-dispatch.md)) and [issue-3628-single-endpoint D-020](../specs/issue-3628-single-endpoint.md) disagree about the CAS transport, and neither has a "replaces" note. The code follows D-020. Proposed: write a spec change that records D-020 as the replacement of dispatch D-003. From b3b44cecfd1f043da386fd73b95e96d59206e04e Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino Date: Sun, 1 Nov 2026 10:55:26 +0100 Subject: [PATCH 2/6] docs(decisions): use area-prefixed decision IDs Follow the new Current Decisions format: each area gets a three-letter prefix (CAS, ATT, AUT, OPS, TRC) and its own ID sequence from 001, and the pages use the new agent instructions comment. The IDs were not yet merged or cited, so they are renumbered rather than kept. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino Chainloop-Trace-Sessions: e3100522-144f-4450-91f7-eed1cd2c29d9 --- docs/decisions/README.md | 15 +++-- docs/decisions/artifact-cas.md | 22 +++---- docs/decisions/attestation-process.md | 14 ++-- docs/decisions/authentication.md | 20 +++--- docs/decisions/chainloop-trace.md | 92 +++++++++++++-------------- docs/decisions/service-operations.md | 18 +++--- 6 files changed, 93 insertions(+), 88 deletions(-) diff --git a/docs/decisions/README.md b/docs/decisions/README.md index a98ce4c16..cc8a9bee7 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -3,7 +3,7 @@ AGENT INSTRUCTIONS. Keep this comment. Read it before you change this file. The br:eng-adr skill writes this page. Do not edit it by hand, and do not change it as part of other work. A person approves each change before the skill writes it, and the area owner reviews it in a PR. -Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### ADR-NNN: " entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. +Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### <PFX>-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. - Current decisions and Repository-wide decisions: the rules that hold today. Follow them. The page holds only choices that a reader of the code would likely get wrong. It does not describe how the system works: for that, read the code or the specs. - Non-Confirmed: decisions that the code does not show yet, for example a plan in a spec. They are not rules yet. - Superseded: old decisions, struck through, each with the ID of the decision that replaced it. They are history only. @@ -12,9 +12,9 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi - Before you change the behavior of an area, read its current decisions and its Language. The page leaves out smaller decisions (the "Not shown" line), so also check the Decision Records of the specs that the page lists. Read the linked spec for the reasons. - To record a decision that the team made and built: run br:eng-adr new. -- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces ADR-NNN". +- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces <PFX>-NNN". - To refresh this page from the specs and the code: run br:eng-adr reconcile. -- Never renumber or reuse an ADR-NNN ID. Never delete a decision: supersede it. +- Never renumber or reuse a decision ID. Never delete a decision: supersede it. - When your code goes against a current decision, say so in your PR. Do not edit this page to match the code. --> @@ -28,6 +28,7 @@ Last refresh: 2026-11-01, commit 4e150ca7. ### Artifact CAS - **Page:** [artifact-cas.md](artifact-cas.md) +- **Prefix:** CAS - **Owner:** migmartri - **Purpose:** Content-addressable storage of evidence blobs, with its upload and download paths and its caches. - **Does not own:** the listeners, ports, TLS, and ingress of the CAS (Service Operations), and the download tokens that the control plane makes (Authentication). @@ -36,6 +37,7 @@ Last refresh: 2026-11-01, commit 4e150ca7. ### Attestation Process - **Page:** [attestation-process.md](attestation-process.md) +- **Prefix:** ATT - **Owner:** migmartri - **Purpose:** The attestation lifecycle of the CLI (`init`, `add`, `status`, `push`, `reset`, `verify`), and how it selects its organization and project. - **Does not own:** AI coding session evidence (Chainloop Trace), and login and tokens (Authentication). @@ -44,6 +46,7 @@ Last refresh: 2026-11-01, commit 4e150ca7. ### Authentication - **Page:** [authentication.md](authentication.md) +- **Prefix:** AUT - **Owner:** migmartri - **Purpose:** User login through the control plane, the login callback, and the hand-off of the session token to the CLI and other clients. - **Does not own:** the CAS download token header (Artifact CAS), and the organization that a command uses (Attestation Process). @@ -52,6 +55,7 @@ Last refresh: 2026-11-01, commit 4e150ca7. ### Service Operations - **Page:** [service-operations.md](service-operations.md) +- **Prefix:** OPS - **Owner:** migmartri - **Purpose:** The network endpoints of the control plane and the CAS: listeners, protocols, TLS, internal ports, and the Helm chart ingress. - **Does not own:** what the endpoints serve (Artifact CAS, Authentication). @@ -60,6 +64,7 @@ Last refresh: 2026-11-01, commit 4e150ca7. ### Chainloop Trace - **Page:** [chainloop-trace.md](chainloop-trace.md) +- **Prefix:** TRC - **Owner:** jiparis - **Purpose:** Recording AI coding sessions as evidence: providers, spec and skill capture, transcript pointers, line attribution, local mode, and the e2e test matrix. - **Does not own:** the attestation lifecycle and the organization order of `.chainloop.yml` (Attestation Process), and blob storage (Artifact CAS). @@ -68,8 +73,8 @@ Last refresh: 2026-11-01, commit 4e150ca7. ## Relationships -- **Attestation Process → Chainloop Trace:** both read `.chainloop.yml` through one reader (ADR-003, attestation-process.md). A change to the file order or keys changes both areas. -- **Chainloop Trace → Artifact CAS:** Trace uploads each spec source, skill package, and pasted image as its own material. A transcript pointer replaces a copy only when its material is stored (ADR-028, chainloop-trace.md). +- **Attestation Process → Chainloop Trace:** both read `.chainloop.yml` through one reader (ATT-003, attestation-process.md). A change to the file order or keys changes both areas. +- **Chainloop Trace → Artifact CAS:** Trace uploads each spec source, skill package, and pasted image as its own material. A transcript pointer replaces a copy only when its material is stored (TRC-016, chainloop-trace.md). - **Service Operations → Artifact CAS, Authentication:** the shared endpoint carries the CAS ByteStream and download routes and the login endpoints. A change to the dispatch or TLS affects all of them. ## Repository-wide decisions diff --git a/docs/decisions/artifact-cas.md b/docs/decisions/artifact-cas.md index 31ba33bfd..818cf082d 100644 --- a/docs/decisions/artifact-cas.md +++ b/docs/decisions/artifact-cas.md @@ -3,7 +3,7 @@ AGENT INSTRUCTIONS. Keep this comment. Read it before you change this file. The br:eng-adr skill writes this page. Do not edit it by hand, and do not change it as part of other work. A person approves each change before the skill writes it, and the area owner reviews it in a PR. -Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### ADR-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. +Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### <PFX>-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. - Current decisions and Repository-wide decisions: the rules that hold today. Follow them. The page holds only choices that a reader of the code would likely get wrong. It does not describe how the system works: for that, read the code or the specs. - Non-Confirmed: decisions that the code does not show yet, for example a plan in a spec. They are not rules yet. - Superseded: old decisions, struck through, each with the ID of the decision that replaced it. They are history only. @@ -12,9 +12,9 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi - Before you change the behavior of an area, read its current decisions and its Language. The page leaves out smaller decisions (the "Not shown" line), so also check the Decision Records of the specs that the page lists. Read the linked spec for the reasons. - To record a decision that the team made and built: run br:eng-adr new. -- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces ADR-NNN". +- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces <PFX>-NNN". - To refresh this page from the specs and the code: run br:eng-adr reconcile. -- Never renumber or reuse an ADR-NNN ID. Never delete a decision: supersede it. +- Never renumber or reuse a decision ID. Never delete a decision: supersede it. - When your code goes against a current decision, say so in your PR. Do not edit this page to match the code. --> @@ -31,7 +31,7 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ## Current decisions -### ADR-016: Existence cache in NATS KV when connected, in memory otherwise +### CAS-001: Existence cache in NATS KV when connected, in memory otherwise The existence cache uses the shared cache library. It uses NATS KV when the CAS has a NATS connection, and an in-memory LRU when it does not. - Why: replicas share hits when NATS is there, and the CAS gets no new dependency when it is not. - Rejected: memory only (the hit rate drops as replicas grow). NATS required (a hard dependency for a speed gain). @@ -39,7 +39,7 @@ The existence cache uses the shared cache library. It uses NATS KV when the CAS - Source: [issue-3543-cas-existence-cache D-001](../specs/issue-3543-cas-existence-cache.md) - Code: verified: `pkg/cache/casexistence/casexistence.go`, `app/artifact-cas/cmd/wire.go`. -### ADR-017: The existence cache lives in the CAS, server side +### CAS-002: The existence cache lives in the CAS, server side The CAS ByteStream write checks the cache and answers "already exists" with no backend call. The CLI has no cache. - Why: it works for all CLI versions and all clients, with no API change. - Rejected: a cache in the CLI (each command is a new process, so nothing is reused). @@ -47,7 +47,7 @@ The CAS ByteStream write checks the cache and answers "already exists" with no b - Source: [issue-3543-cas-existence-cache D-003](../specs/issue-3543-cas-existence-cache.md) - Code: verified: `app/artifact-cas/internal/service/bytestream.go`, `app/artifact-cas/internal/service/cache.go`. -### ADR-018: The cache key starts with the organization +### CAS-003: The cache key starts with the organization The key is the organization, then the backend type, then a hash of the secret reference, then the digest. The cache has its own bucket. - Why: the organization prefix keeps the entries of one tenant away from all other tenants, also on shared storage. The secret reference separates backends in one organization. - Rejected: the digest alone (the upload of one tenant answers for another tenant, and shows that the other tenant has the file). The backend and the digest without the organization (a shared backend leaks the same information). @@ -55,14 +55,14 @@ The key is the organization, then the backend type, then a hash of the secret re - Source: [issue-3543-cas-existence-cache D-004](../specs/issue-3543-cas-existence-cache.md) - Code: verified: `pkg/cache/casexistence/casexistence.go`. `app/artifact-cas/internal/service/cache.go` skips the cache when the organization is empty. -### ADR-019: Credentials never go to a shared store +### CAS-004: Credentials never go to a shared store The client cache, which holds backend credentials, stays in process memory. It never uses NATS. - Why: credentials must not leave the process. - Deciders: migmartri, javirln - Source: [issue-3543-cas-existence-cache D-006](../specs/issue-3543-cas-existence-cache.md) - Code: verified: `app/artifact-cas/internal/service/cache.go` (in-process expirable LRU). -### ADR-020: Downloads use the browser HTTP cache +### CAS-005: Downloads use the browser HTTP cache The CAS sets cache headers on downloads and answers 304 when the browser has the content. The control plane keeps no copy of the bytes. - Why: the browser downloads straight from the CAS, so a cache there skips the backend copy and the transfer. - Rejected: a server cache in the control plane (the control plane never sees the bytes, and its message size limit is too small). @@ -70,7 +70,7 @@ The CAS sets cache headers on downloads and answers 304 when the browser has the - Source: [issue-3549-cas-download-browser-cache D-001](../specs/issue-3549-cas-download-browser-cache.md) - Code: verified: `app/artifact-cas/internal/service/download.go`. -### ADR-021: The download token goes in the Authorization header +### CAS-006: The download token goes in the Authorization header The CAS accepts the download token in an `Authorization: Bearer` header, so the download URL stays the same between calls. - Why: the browser cache uses the URL as its key, and the token changes on each call. - Rejected: one token for each user and digest (the URL stays the same only for the life of the token). @@ -78,7 +78,7 @@ The CAS accepts the download token in an `Authorization: Bearer` header, so the - Source: [issue-3549-cas-download-browser-cache D-003](../specs/issue-3549-cas-download-browser-cache.md) - Code: verified on the CAS side: `pkg/middlewares/http/jwt.go`, CORS allows `Authorization`. The control plane `GetDownloadURL` still puts the token in `?t=` (`app/controlplane/internal/service/casredirect.go`). The client that moves the token to the header is not in this repository. -### ADR-022: The header token wins, with no fallback to the query +### CAS-007: The header token wins, with no fallback to the query When a request has a token in the header and in the query, the CAS uses only the header. A bad header fails with 401. - Why: a client that sends a header sets its intent. - Rejected: reject a request with both (the viewer must then strip the query). The query wins (the header has no effect for a URL that still holds a token). @@ -86,7 +86,7 @@ When a request has a token in the header and in the query, the CAS uses only the - Source: [issue-3549-cas-download-browser-cache D-004](../specs/issue-3549-cas-download-browser-cache.md) - Code: verified: `pkg/middlewares/http/jwt.go`. -### ADR-023: The CAS checks that the object exists before a 304 +### CAS-008: The CAS checks that the object exists before a 304 Before it answers 304, the CAS makes a metadata call to the backend. A deleted object gives 404. - Why: a deleted object stops showing at once, and the metadata call is cheap. - Rejected: a 304 from the digest alone (a browser can show a deleted object until it drops its copy). diff --git a/docs/decisions/attestation-process.md b/docs/decisions/attestation-process.md index 7737c65bb..db28effc8 100644 --- a/docs/decisions/attestation-process.md +++ b/docs/decisions/attestation-process.md @@ -3,7 +3,7 @@ AGENT INSTRUCTIONS. Keep this comment. Read it before you change this file. The br:eng-adr skill writes this page. Do not edit it by hand, and do not change it as part of other work. A person approves each change before the skill writes it, and the area owner reviews it in a PR. -Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### ADR-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. +Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### <PFX>-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. - Current decisions and Repository-wide decisions: the rules that hold today. Follow them. The page holds only choices that a reader of the code would likely get wrong. It does not describe how the system works: for that, read the code or the specs. - Non-Confirmed: decisions that the code does not show yet, for example a plan in a spec. They are not rules yet. - Superseded: old decisions, struck through, each with the ID of the decision that replaced it. They are history only. @@ -12,9 +12,9 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi - Before you change the behavior of an area, read its current decisions and its Language. The page leaves out smaller decisions (the "Not shown" line), so also check the Decision Records of the specs that the page lists. Read the linked spec for the reasons. - To record a decision that the team made and built: run br:eng-adr new. -- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces ADR-NNN". +- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces <PFX>-NNN". - To refresh this page from the specs and the code: run br:eng-adr reconcile. -- Never renumber or reuse an ADR-NNN ID. Never delete a decision: supersede it. +- Never renumber or reuse a decision ID. Never delete a decision: supersede it. - When your code goes against a current decision, say so in your PR. Do not edit this page to match the code. --> @@ -31,7 +31,7 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ## Current decisions -### ADR-001: Organization order is flag, environment variable, repository config, saved default +### ATT-001: Organization order is flag, environment variable, repository config, saved default The `--org` flag and its environment variable win. The repository config applies only when neither is set, and it comes before the saved default. - Why: the file pins the organization for the repository. - Rejected: the saved default before the file (most users have a saved default, so the file would almost never apply). @@ -39,7 +39,7 @@ The `--org` flag and its environment variable win. The repository config applies - Source: [issue-3063-attestation-repo-config D-002](../specs/issue-3063-attestation-repo-config.md) - Code: verified: `app/cli/cmd/attestation.go`, `app/cli/cmd/root.go`. For a command other than init with no remote state, the organization of the local attestation state also comes before the saved default (`attestation.go`). -### ADR-003: One reader of `.chainloop.yml` for attestation and trace +### ATT-003: One reader of `.chainloop.yml` for attestation and trace Attestation commands and Trace read the repository config through one package. - Why: two readers already disagreed on the file order. - Rejected: a second change to the attestation reader only. @@ -47,7 +47,7 @@ Attestation commands and Trace read the repository config through one package. - Source: [issue-3063-attestation-repo-config D-007](../specs/issue-3063-attestation-repo-config.md) - Code: verified for reads: `app/cli/internal/repositoryconfig/config.go`, used by `app/cli/cmd/attestation.go` and `app/cli/internal/trace/config/config.go`. Writes still use their own lookup. -### ADR-004: The repository config organization acts as `--org`, with a check for API tokens +### ATT-004: The repository config organization acts as `--org`, with a check for API tokens The organization from the file has the same effect as `--org` for the run. It is not saved as the default. When an API token belongs to a different organization, the command stops with an error that names both. - Why: one rule for users: the file is a `--org` that the repository keeps. - Rejected: an exact copy of `--org` (with an API token, it ignores a different organization with no message). A warning only (CI logs often hide it). @@ -59,7 +59,7 @@ Not shown: 6 smaller decisions of [issue-3063-attestation-repo-config](../specs/ ## Non-Confirmed -### ADR-002: All attestation commands that contact the control plane read the organization from the file +### ATT-002: All attestation commands that contact the control plane read the organization from the file - Why: with remote state, a command must connect to the correct organization before it can load the state. - Deciders: migmartri, javirln - Source: [issue-3063-attestation-repo-config D-004](../specs/issue-3063-attestation-repo-config.md) diff --git a/docs/decisions/authentication.md b/docs/decisions/authentication.md index 2db260f46..f9f6775ae 100644 --- a/docs/decisions/authentication.md +++ b/docs/decisions/authentication.md @@ -3,7 +3,7 @@ AGENT INSTRUCTIONS. Keep this comment. Read it before you change this file. The br:eng-adr skill writes this page. Do not edit it by hand, and do not change it as part of other work. A person approves each change before the skill writes it, and the area owner reviews it in a PR. -Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### ADR-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. +Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### <PFX>-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. - Current decisions and Repository-wide decisions: the rules that hold today. Follow them. The page holds only choices that a reader of the code would likely get wrong. It does not describe how the system works: for that, read the code or the specs. - Non-Confirmed: decisions that the code does not show yet, for example a plan in a spec. They are not rules yet. - Superseded: old decisions, struck through, each with the ID of the decision that replaced it. They are history only. @@ -12,9 +12,9 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi - Before you change the behavior of an area, read its current decisions and its Language. The page leaves out smaller decisions (the "Not shown" line), so also check the Decision Records of the specs that the page lists. Read the linked spec for the reasons. - To record a decision that the team made and built: run br:eng-adr new. -- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces ADR-NNN". +- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces <PFX>-NNN". - To refresh this page from the specs and the code: run br:eng-adr reconcile. -- Never renumber or reuse an ADR-NNN ID. Never delete a decision: supersede it. +- Never renumber or reuse a decision ID. Never delete a decision: supersede it. - When your code goes against a current decision, say so in your PR. Do not edit this page to match the code. --> @@ -31,7 +31,7 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ## Current decisions -### ADR-037: The callback gets a single-use handoff code, not the token +### AUT-001: The callback gets a single-use handoff code, not the token When the client sends a challenge, the login callback gets a single-use code. The client exchanges the code for the session token. - Why: it takes the token out of the URL. - Rejected: keep `?t=` and add checks (the token is still in the URL). The token in the URL fragment (still in history, and fixation stays). A full OAuth server with refresh tokens (a much larger change). @@ -39,7 +39,7 @@ When the client sends a challenge, the login callback gets a single-use code. Th - Source: [issue-3626-login-code-exchange D-001](../specs/issue-3626-login-code-exchange.md) - Code: verified: `app/controlplane/internal/service/auth.go`, `app/controlplane/pkg/biz/loginhandoff.go`, `app/cli/cmd/auth_login.go`. -### ADR-038: The code is bound to an S256 challenge and the callback URL +### AUT-002: The code is bound to an S256 challenge and the callback URL The client sends a PKCE-style S256 challenge. The exchange checks the verifier and the callback URL. - Why: it works for the CLI and for a browser app, and needs no cookie at exchange time. - Rejected: bind to the callback URL only (a web app has a fixed callback, so fixation stays). Require the OIDC state cookie at exchange (the CLI and a cross-origin web app do not have it). @@ -47,7 +47,7 @@ The client sends a PKCE-style S256 challenge. The exchange checks the verifier a - Source: [issue-3626-login-code-exchange D-002](../specs/issue-3626-login-code-exchange.md) - Code: verified: `app/controlplane/pkg/biz/loginhandoff.go` (`Issue`, `Exchange`), `app/cli/cmd/auth_login.go`. -### ADR-039: The code store is a database table with no token at rest +### AUT-003: The code store is a database table with no token at rest The table holds the code hash, the user, the expiry, and the challenge. One conditional update consumes the code. The control plane makes the token at exchange time. - Why: it works with many replicas and enforces single use. - Rejected: store the token with the code (a token at rest in the database). Memory (fails with more than one replica). A signed code with no store (cannot enforce single use). @@ -55,7 +55,7 @@ The table holds the code hash, the user, the expiry, and the challenge. One cond - Source: [issue-3626-login-code-exchange D-004](../specs/issue-3626-login-code-exchange.md) - Code: verified: `app/controlplane/pkg/data/loginhandoff.go`, `app/controlplane/internal/service/auth.go`. -### ADR-040: Opt-in per request, with flags on the info endpoint +### AUT-004: Opt-in per request, with flags on the info endpoint The server issues a code only when the request has a challenge. The info endpoint reports whether the exchange is supported and whether the legacy callback is allowed. - Why: old CLIs need no change. A new CLI knows what to expect and can detect a downgrade. - Rejected: opt-in with no flag (the CLI cannot tell a downgrade from an old server). A version check (fails with forks and development builds). @@ -63,7 +63,7 @@ The server issues a code only when the request has a challenge. The info endpoin - Source: [issue-3626-login-code-exchange D-005](../specs/issue-3626-login-code-exchange.md) - Code: verified: `app/controlplane/api/controlplane/v1/status.proto`, `app/controlplane/internal/service/status.go`, `app/cli/cmd/auth_login.go`. -### ADR-041: The CLI accepts `?t=` only while the server allows the legacy callback +### AUT-005: The CLI accepts `?t=` only while the server allows the legacy callback When the CLI sent a challenge and gets `?t=`, it accepts the token with a warning while the server reports legacy allowed. It refuses the token when legacy is denied. - Why: a login page that does not forward the new parameters yet must not break login. The loopback listener has a random port and a short life, so the fixation risk is low. - Rejected: refuse `?t=` from the first CLI release (it breaks login behind such a login page). @@ -71,7 +71,7 @@ When the CLI sent a challenge and gets `?t=`, it accepts the token with a warnin - Source: [issue-3626-login-code-exchange D-007](../specs/issue-3626-login-code-exchange.md) - Code: verified: `app/cli/cmd/auth_login.go`. -### ADR-042: One error to the client for each exchange failure +### AUT-006: One error to the client for each exchange failure Each exchange failure gives the client the same error. The specific reason goes to the server log only. - Why: a uniform client error gives no hint to a person who guesses codes. The server log reason helps operators debug failed logins. - Rejected: a uniform error with no server log reason (hard to debug). @@ -79,7 +79,7 @@ Each exchange failure gives the client the same error. The specific reason goes - Source: [issue-3626-login-code-exchange D-012](../specs/issue-3626-login-code-exchange.md) - Code: verified: `app/controlplane/internal/service/auth.go`. -### ADR-043: The exchange endpoint is gRPC only +### AUT-007: The exchange endpoint is gRPC only The exchange is an unauthenticated RPC on the auth service, with no HTTP mapping. - Why: the CLI uses gRPC and the web UI uses gRPC-Web, so no client needs a plain HTTP route. With no HTTP route, the public surface of the unauthenticated endpoint is smaller, and the web UI makes no cross-origin preflight. - Rejected: keep an HTTP mapping for a future non-gRPC client (no such client exists). diff --git a/docs/decisions/chainloop-trace.md b/docs/decisions/chainloop-trace.md index ba175bc96..9f6830dbf 100644 --- a/docs/decisions/chainloop-trace.md +++ b/docs/decisions/chainloop-trace.md @@ -3,7 +3,7 @@ AGENT INSTRUCTIONS. Keep this comment. Read it before you change this file. The br:eng-adr skill writes this page. Do not edit it by hand, and do not change it as part of other work. A person approves each change before the skill writes it, and the area owner reviews it in a PR. -Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### ADR-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. +Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### <PFX>-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. - Current decisions and Repository-wide decisions: the rules that hold today. Follow them. The page holds only choices that a reader of the code would likely get wrong. It does not describe how the system works: for that, read the code or the specs. - Non-Confirmed: decisions that the code does not show yet, for example a plan in a spec. They are not rules yet. - Superseded: old decisions, struck through, each with the ID of the decision that replaced it. They are history only. @@ -12,9 +12,9 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi - Before you change the behavior of an area, read its current decisions and its Language. The page leaves out smaller decisions (the "Not shown" line), so also check the Decision Records of the specs that the page lists. Read the linked spec for the reasons. - To record a decision that the team made and built: run br:eng-adr new. -- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces ADR-NNN". +- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces <PFX>-NNN". - To refresh this page from the specs and the code: run br:eng-adr reconcile. -- Never renumber or reuse an ADR-NNN ID. Never delete a decision: supersede it. +- Never renumber or reuse a decision ID. Never delete a decision: supersede it. - When your code goes against a current decision, say so in your PR. Do not edit this page to match the code. --> @@ -35,16 +35,16 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ## Current decisions -### ADR-005: The agent decides what the spec is +### TRC-001: The agent decides what the spec is An instruction at session start tells the agent to resolve the spec of the session and write it into the spec folder. - Why: only the agent knows which input sets the task, and it reaches sources that the transcript never holds. - Rejected: extraction from the transcript by the CLI. It needs rules to pick the spec, misses sources that the agent never opened, and needs one extractor for each agent. Extraction on the server after ingestion (the spec is not in the attestation). - Deciders: jiparis, migmartri - Source: [issue-3495-session-spec-capture D-001](../specs/issue-3495-session-spec-capture.md) - Code: verified: `app/cli/pkg/action/trace_spec.go`, `app/cli/pkg/action/trace_agent_hook.go`. -- Partly replaced by ADR-013: the CLI makes the skill entries. +- Partly replaced by TRC-009: the CLI makes the skill entries. -### ADR-006: A capture is Markdown with a short header +### TRC-002: A capture is Markdown with a short header A capture is a Markdown body under a frontmatter header (kind, uri, role, title, description). With no header or a bad header, the whole file is content. An unknown kind becomes `text`. - Why: a model writes long Markdown reliably. Long text escaped inside JSON fails completely when one character is wrong. A bad header still keeps the content. - Rejected: JSON files. @@ -52,7 +52,7 @@ A capture is a Markdown body under a frontmatter header (kind, uri, role, title, - Source: [issue-3495-session-spec-capture D-003](../specs/issue-3495-session-spec-capture.md) - Code: verified: `app/cli/internal/trace/spec/parse.go`. -### ADR-007: The agent does not capture a pasted image +### TRC-003: The agent does not capture a pasted image The instructions tell the agent not to capture an image pasted into the conversation, also when it has a file path for it. - Why: the agent cannot copy the bytes of a pasted image, and a description that the agent writes is not the image. - Rejected: a description of kind `image` (the earlier rule of issue-3495 D-010). @@ -60,7 +60,7 @@ The instructions tell the agent not to capture an image pasted into the conversa - Source: [issue-3515-spec-capture-during-session D-009](../specs/issue-3515-spec-capture-during-session.md) - Code: verified: `app/cli/pkg/action/trace_spec.go`, `app/cli/internal/trace/README.md`. See Open conflicts. -### ADR-008: Pi integration is a project-local TypeScript extension +### TRC-004: Pi integration is a project-local TypeScript extension `trace init` installs a marked extension at `.pi/extensions/chainloop-trace.ts`, which sends Pi lifecycle and tool events to the Trace hooks. - Why: Pi documents lifecycle and tool events for extensions. - Rejected: a Pi fork, or a file watcher. @@ -68,7 +68,7 @@ The instructions tell the agent not to capture an image pasted into the conversa - Source: [issue-3520-pi-trace-provider D-001](../specs/issue-3520-pi-trace-provider.md) - Code: verified: `app/cli/internal/trace/pi/hooks.go`. -### ADR-009: The Pi provider parses a copy of the Pi JSONL v3 transcript +### TRC-005: The Pi provider parses a copy of the Pi JSONL v3 transcript The provider keeps a private copy of the Pi session file and parses only that copy. - Why: the Pi transcript is the authoritative record of provider, model, usage, tool, and conversation data. - Rejected: a duplicate transcript that the extension writes. @@ -76,14 +76,14 @@ The provider keeps a private copy of the Pi session file and parses only that co - Source: [issue-3520-pi-trace-provider D-006](../specs/issue-3520-pi-trace-provider.md) - Code: verified: `app/cli/internal/trace/pi/provider.go`, `app/cli/internal/trace/pi/parse.go`. -### ADR-010: File snapshot keys include the tool call ID +### TRC-006: File snapshot keys include the tool call ID The hook input field `tool_use_id` is part of the file snapshot key when the provider sends it. Without it, the key is the session and the path. - Why: it prevents an overwrite between concurrent tool calls, without a second name for one value and without the subagent identity. - Deciders: waveywaves, migmartri, jiparis - Source: [issue-3520-pi-trace-provider D-012](../specs/issue-3520-pi-trace-provider.md) - Code: verified: `app/cli/internal/trace/state/snapshot.go`. Pi, Claude Code, and Codex send it. The OpenCode key has no tool call ID (`app/cli/internal/trace/opencode/provider.go`). -### ADR-011: A binary source gets its role from a companion file +### TRC-007: A binary source gets its role from a companion file For a binary file, the agent writes `<name>.meta.yaml` next to it, with the role, the title, and the description. - Why: the agent decides the role of every source, also of an image. - Rejected: the CLI sets `reference` for each image (a guess, and a mockup can be the spec). No role for binary files. @@ -91,7 +91,7 @@ For a binary file, the agent writes `<name>.meta.yaml` next to it, with the role - Source: [issue-3531-spec-source-role D-003](../specs/issue-3531-spec-source-role.md) - Code: verified: `app/cli/internal/trace/spec/spec.go`, `app/cli/pkg/action/trace_spec_materials.go`. -### ADR-012: A used skill is a spec entry of kind `skill` +### TRC-008: A used skill is a spec entry of kind `skill` Each skill that the session used is an entry of kind `skill` in the spec list of the session. - Why: a skill is a source of the session, like a ticket or a plan. It gets the same storage, redaction, and annotations. - Rejected: a separate `skills_used` list in the session material. @@ -99,14 +99,14 @@ Each skill that the session used is an entry of kind `skill` in the spec list of - Source: [issue-3540-trace-skills-used D-001](../specs/issue-3540-trace-skills-used.md) - Code: verified: `pkg/attestation/crafter/materials/aicodingsession/aicodingsession.go`, `app/cli/pkg/action/trace_skills.go`. -### ADR-013: The CLI makes the skill entries from the transcript +### TRC-009: The CLI makes the skill entries from the transcript The CLI finds skill use in the transcript and the hook events. The agent does not capture skills. -- Why: skill use is a fact in the transcript, and no rule must pick it. The reason of ADR-005 (the CLI cannot tell which input sets the task) does not apply to skills. +- Why: skill use is a fact in the transcript, and no rule must pick it. The reason of TRC-001 (the CLI cannot tell which input sets the task) does not apply to skills. - Deciders: jiparis, migmartri - Source: [issue-3540-trace-skills-used D-002](../specs/issue-3540-trace-skills-used.md) - Code: verified for Claude Code, Codex, and OpenCode: `app/cli/internal/trace/claude/skills.go`, `app/cli/pkg/action/trace_skills.go`. Cursor and Pi have no skill tracking. -### ADR-014: The whole skill folder is recorded +### TRC-010: The whole skill folder is recorded The CLI copies the full folder of a used skill, not only the text that the agent loaded. - Why: a skill can run its own scripts, and a reviewer needs that code. The attestation must not depend on the `ai-agent-config` material. - Rejected: the `SKILL.md` text from the transcript (no scripts, and Claude Code deletes the frontmatter). @@ -114,7 +114,7 @@ The CLI copies the full folder of a used skill, not only the text that the agent - Source: [issue-3540-trace-skills-used D-004](../specs/issue-3540-trace-skills-used.md) - Code: verified: `app/cli/pkg/action/trace_skills.go`, `app/cli/internal/trace/skill/skill.go`. -### ADR-015: One reproducible archive for each skill +### TRC-011: One reproducible archive for each skill Each skill gives one archive with fixed file times, stored as one package material. - Why: one package digest for each skill, and the same skill version gives the same digest in all sessions. - Rejected: one material for each file (it fills the attestation for a skill with many files). @@ -122,7 +122,7 @@ Each skill gives one archive with fixed file times, stored as one package materi - Source: [issue-3540-trace-skills-used D-006](../specs/issue-3540-trace-skills-used.md) - Code: verified: `app/cli/internal/trace/skill/skill.go`, `app/cli/pkg/action/trace_skills.go`. -### ADR-024: Copies of a source become digest pointers in the transcript +### TRC-012: Copies of a source become digest pointers in the transcript The content stays in the spec material. Each copy in the transcript is replaced in place by a pointer with the digest. - Why: the transcript gets small and keeps the position of each copy. The content stays in the evidence. - Rejected: drop the copies (a reader loses the position). Delete only the second copy of read files (the transcript is still large, and the redaction still scans the bytes). @@ -130,7 +130,7 @@ The content stays in the spec material. Each copy in the transcript is replaced - Source: [issue-3556-session-image-placeholders D-001](../specs/issue-3556-session-image-placeholders.md) - Code: verified for Claude Code and Codex: `app/cli/internal/trace/pointer/pointer.go`, `app/cli/internal/trace/claude/speccopies.go`. Cursor, OpenCode, and Pi have no replacer. -### ADR-025: The CLI replaces copies at push time, before redaction +### TRC-013: The CLI replaces copies at push time, before redaction The replacement runs in the CLI at push time, before the CLI records and redacts the session material. - Why: only the CLI can read the file on disk for the match, and the redaction then never scans the replaced bytes. - Rejected: in the control plane after the upload (the bytes are already uploaded and scanned, and the file is not on disk). @@ -138,7 +138,7 @@ The replacement runs in the CLI at push time, before the CLI records and redacts - Source: [issue-3556-session-image-placeholders D-002](../specs/issue-3556-session-image-placeholders.md) - Code: verified: `app/cli/pkg/action/trace_hook_handler.go`. -### ADR-026: A pointer replaces the content in place and keeps the block type +### TRC-014: A pointer replaces the content in place and keeps the block type The pointer takes the place of the content inside the same transcript block, which keeps its type and position. - Why: a transcript view finds each copy where it was, with no join to other data. - Rejected: a separate list of replacements in the session material (the view must join the list to the transcript). @@ -146,7 +146,7 @@ The pointer takes the place of the content inside the same transcript block, whi - Source: [issue-3556-session-image-placeholders D-003](../specs/issue-3556-session-image-placeholders.md) - Code: verified: `app/cli/internal/trace/claude/pastedimages.go`, `app/cli/internal/trace/pointer/pointer.go`. -### ADR-027: One finder for each kind of copy, with optional pointer fields +### TRC-015: One finder for each kind of copy, with optional pointer fields Each kind of copy (file read, file write, pasted image) has its own finder, and all use the same pointer. A later kind adds optional fields. - Why: other inputs can use the same mechanism later. - Rejected: an image-only pointer (each new kind needs a new format and a new view). A version and a kind field in each pointer (optional fields are enough, and the material holds the kind). @@ -154,7 +154,7 @@ Each kind of copy (file read, file write, pasted image) has its own finder, and - Source: [issue-3556-session-image-placeholders D-008](../specs/issue-3556-session-image-placeholders.md) - Code: verified: `app/cli/internal/trace/pointer/pointer.go`. -### ADR-028: Replace a copy only on an exact digest match +### TRC-016: Replace a copy only on an exact digest match The CLI replaces a copy only when the material is stored and its digest equals the digest of the copy. - Why: the transcript is evidence, and a wrong pointer loses content with no way back. - Rejected: a best-effort match, for example by size or file name (a wrong match deletes content that is stored nowhere else). @@ -162,7 +162,7 @@ The CLI replaces a copy only when the material is stored and its digest equals t - Source: [issue-3556-session-image-placeholders D-010](../specs/issue-3556-session-image-placeholders.md) - Code: verified: `app/cli/internal/trace/pointer/pointer.go`, `app/cli/internal/trace/claude/speccopies.go`. -### ADR-029: Each exact copy of a spec source is replaced, for all file types +### TRC-017: Each exact copy of a spec source is replaced, for all file types The replacement covers each exact copy of a source in the spec list, from file reads and file writes, for all file types. Other media stay inline. - Why: one rule covers images and text sources. Each pointer resolves in the session itself, and the CLI never decides on its own that content is evidence. - Rejected: images only (the write copies of tickets stay, and the redaction still scans them). Upload each other media block too (the agent did not choose it). @@ -170,7 +170,7 @@ The replacement covers each exact copy of a source in the spec list, from file r - Source: [issue-3556-session-image-placeholders D-013](../specs/issue-3556-session-image-placeholders.md) - Code: verified: `app/cli/internal/trace/claude/speccopies.go`, `app/cli/pkg/action/trace_spec_materials.go`. -### ADR-030: The CLI captures pasted images at push time +### TRC-018: The CLI captures pasted images at push time At push time, the CLI makes an image spec entry for each pasted image in the transcript, then replaces its inline copy with a pointer. - Why: the bytes are in the transcript, so the capture depends on neither the agent nor a file on disk. - Rejected: replace only the images that the agent captured. Images then stay inline when the agent does not copy them, and the agent can copy only from a file path. @@ -178,7 +178,7 @@ At push time, the CLI makes an image spec entry for each pasted image in the tra - Source: [issue-3569-pasted-image-pointers D-001](../specs/issue-3569-pasted-image-pointers.md) - Code: verified for Claude Code and Codex: `app/cli/pkg/action/trace_pasted_images.go`, `app/cli/pkg/action/trace_hook_handler.go`, `app/cli/internal/trace/codex/materials.go`. -### ADR-032: Pasted image bytes come from the transcript image block +### TRC-020: Pasted image bytes come from the transcript image block The CLI decodes the image data from the transcript block that the model got. - Why: the data is what the model got, and it is always there. - Rejected: read the clipboard file that the agent names. It is a temporary file that can be gone at push time, and its format is specific to one agent. @@ -186,7 +186,7 @@ The CLI decodes the image data from the transcript block that the model got. - Source: [issue-3569-pasted-image-pointers D-005](../specs/issue-3569-pasted-image-pointers.md) - Code: verified: `app/cli/internal/trace/provider.go`, `app/cli/internal/trace/claude/pastedimages.go`, `app/cli/internal/trace/codex/materials.go`. -### ADR-033: Local mode writes an evidence set, not a signed attestation +### TRC-021: Local mode writes an evidence set, not a signed attestation Local mode writes the evidence record and its materials, with no envelope and no signature. - Why: a developer and a test need the evidence, not a signature. - Rejected: a signed bundle with no control plane (it needs a contract and a signing key, a larger and separate effort). @@ -194,7 +194,7 @@ Local mode writes the evidence record and its materials, with no envelope and no - Source: [issue-3582-trace-evidence-export D-002](../specs/issue-3582-trace-evidence-export.md) - Code: verified: `app/cli/pkg/action/trace_local_store.go`. -### ADR-034: `trace init --local` needs no control plane +### TRC-022: `trace init --local` needs no control plane `trace init --local` sets up local mode with no login and no workflow, and writes `traceStorage: LOCAL`. - Why: without it, init needs a login and creates a workflow on the control plane, so a developer cannot use Trace without a backend. - Rejected: a run-only flag with no local init (init still needs the control plane). @@ -202,7 +202,7 @@ Local mode writes the evidence record and its materials, with no envelope and no - Source: [issue-3582-trace-evidence-export D-009](../specs/issue-3582-trace-evidence-export.md) - Code: verified: `app/cli/cmd/trace_init.go`. -### ADR-035: In local mode, the pre-push hook stores the session +### TRC-023: In local mode, the pre-push hook stores the session When the storage mode is local, the pre-push hook stores the sessions on disk and contacts no control plane. - Why: the normal flow already runs at push time, so storage there needs no new wrapper. - Rejected: `trace run --export` as the surface (a single-run command, not the normal push-time path). @@ -210,7 +210,7 @@ When the storage mode is local, the pre-push hook stores the sessions on disk an - Source: [issue-3582-trace-evidence-export D-017](../specs/issue-3582-trace-evidence-export.md) - Code: verified: `app/cli/cmd/trace_hook.go`, `app/cli/pkg/action/trace_hook_handler.go`. -### ADR-036: The index of a stored session is an unsigned in-toto statement +### TRC-024: The index of a stored session is an unsigned in-toto statement A stored session has `statement.json`, rendered by the same Chainloop renderer as a push, with no envelope. - Why: readers and policies that read a Chainloop statement also read a stored session, and the missing envelope shows that it is unsigned. - Rejected: a custom `manifest.json` (a second format that only Trace reads). @@ -218,14 +218,14 @@ A stored session has `statement.json`, rendered by the same Chainloop renderer a - Source: [issue-3582-trace-evidence-export D-021](../specs/issue-3582-trace-evidence-export.md) - Code: verified: `app/cli/pkg/action/trace_local_store.go`. -### ADR-049: The e2e suite runs the real agent CLI, headless, with a real model +### TRC-025: The e2e suite runs the real agent CLI, headless, with a real model - Why: only a real run shows whether the agent fires the hooks, shows the message, and follows the instruction. - Rejected: replay recorded hook events into Trace (cheap and stable, but blind to the agent and the model). Both tiers (the replay tier adds little over the unit tests). - Deciders: jiparis, javirln, migmartri - Source: [issue-3635-trace-e2e-matrix D-001](../specs/issue-3635-trace-e2e-matrix.md) - Code: verified: `app/cli/internal/trace/e2e/claude.go`, `app/cli/internal/trace/e2e/codex.go`. -### ADR-050: The README feature table is the expected matrix +### TRC-026: The README feature table is the expected matrix The e2e suite reads the feature tables in the Trace README as its expected results. It keeps no second list. - Why: one place to keep up to date, and the table must already change in the same PR as a provider change. - Rejected: a separate data file that generates the table (two formats, and more tooling). No expected matrix (a feature that never worked gives no signal). @@ -233,14 +233,14 @@ The e2e suite reads the feature tables in the Trace README as its expected resul - Source: [issue-3635-trace-e2e-matrix D-002](../specs/issue-3635-trace-e2e-matrix.md) - Code: verified: `app/cli/internal/trace/e2e/expected.go`. -### ADR-051: The e2e suite uses local mode only +### TRC-027: The e2e suite uses local mode only - Why: no test organization, no long-lived token in a public repository, and no dependency on a running service. - Rejected: local mode plus a test organization (a secret and a dependency). A control plane started in the job (slow, and more to maintain). - Deciders: jiparis, javirln, migmartri - Source: [issue-3635-trace-e2e-matrix D-007](../specs/issue-3635-trace-e2e-matrix.md) - Code: verified: `app/cli/internal/trace/e2e/workspace.go`, `app/cli/internal/trace/e2e/runner.go`. -### ADR-053: Agent versions are pinned and bumped by hand +### TRC-029: Agent versions are pinned and bumped by hand The suite configuration pins one version for each agent. A maintainer bumps a pin on purpose. - Why: the job has access to secrets and must not run an agent release that nobody selected. - Rejected: the latest release on each run (runs code that nobody selected in a job with secrets). Automated bump pull requests (more tooling for the first version). @@ -248,7 +248,7 @@ The suite configuration pins one version for each agent. A maintainer bumps a pi - Source: [issue-3635-trace-e2e-matrix D-013](../specs/issue-3635-trace-e2e-matrix.md) - Code: verified: `app/cli/internal/trace/e2e/config.yaml`. The suite does not install the agent. It warns when the installed version is not the pinned one. -### ADR-054: Each line range gets an attribution in the existing list +### TRC-030: Each line range gets an attribution in the existing list The ranges stay in the one list of each file entry, and each range gets an attribution, AI or human. - Why: one list keeps the evidence small and gives readers one place to look. The new property is additive. - Rejected: a new list of AI ranges next to the old one (two lists for the same lines). Two new lists for AI and human (a reader must merge them to show a file). @@ -256,21 +256,21 @@ The ranges stay in the one list of each file entry, and each range gets an attri - Source: [issue-3639-line-ranges D-001](../specs/issue-3639-line-ranges.md) - Code: verified: `pkg/attestation/crafter/materials/aicodingsession/aicodingsession.go`, `app/cli/internal/trace/attribution/enrich.go`. -### ADR-055: Range positions are in the pushed commit +### TRC-031: Range positions are in the pushed commit - Why: the lines that later commits replaced are gone at that commit, so the ranges agree with the net counts. A reader of the pull request sees the same version. - Rejected: the last commit of the session (a later commit can move or replace the lines). Ranges for each commit (a reader must combine them). - Deciders: jiparis, migmartri - Source: [issue-3639-line-ranges D-002](../specs/issue-3639-line-ranges.md) - Code: verified: `app/cli/internal/trace/attribution/enrich.go`. -### ADR-056: A range with no attribution is an AI range +### TRC-032: A range with no attribution is an AI range Evidence from an older CLI has ranges with no attribution. Readers treat these as AI ranges at edit time. - Why: platforms that store evidence from older CLIs must read both forms. - Deciders: jiparis, migmartri - Source: [issue-3639-line-ranges D-004](../specs/issue-3639-line-ranges.md) - Code: verified as a documented contract: `pkg/attestation/crafter/materials/aicodingsession/aicodingsession.go`. No reader is in this repository. -### ADR-057: The reported split is the net contribution +### TRC-033: The reported split is the net contribution The AI and human numbers for each file and session are the lines that the session added and that no later commit replaced. - Why: it gives the expected split, and no line counts twice across commits. - Rejected: authored lines for each commit, with no net (a replaced AI line still counts as AI). @@ -278,7 +278,7 @@ The AI and human numbers for each file and session are the lines that the sessio - Source: [issue-3639-trace-line-attribution D-001](../specs/issue-3639-trace-line-attribution.md) - Code: verified: `app/cli/internal/trace/attribution/enrich.go`. -### ADR-058: A replacement sends the owning session again +### TRC-034: A replacement sends the owning session again When a later commit replaces lines of a session, the CLI sends the evidence of that session again. - Why: the platform already replaces session data with newer evidence, so it needs no new concept. - Rejected: separate evidence with no session for human commits (a new concept on the platform). No tracking of commits outside sessions (the human work stays uncounted). @@ -286,7 +286,7 @@ When a later commit replaces lines of a session, the CLI sends the evidence of t - Source: [issue-3639-trace-line-attribution D-002](../specs/issue-3639-trace-line-attribution.md) - Code: verified: `app/cli/pkg/action/trace_replacements.go`, `app/cli/pkg/action/trace_hook_handler.go`. -### ADR-059: Line owners are rebuilt from blame and the ledger +### TRC-035: Line owners are rebuilt from blame and the ledger The line fingerprints go into the existing AI line ledger. At push time, the CLI rebuilds the owner of each line from git blame, the commit records, and the ledger. It stores no owner data for each line. - Why: it reuses the existing storage and pushes nothing to the remote. No second store can disagree with it. - Rejected: git notes on a dedicated ref (push traffic, ref contention, and git configuration). An owner map for each commit (it duplicates the ledger and needs its own rewrite handling). @@ -294,7 +294,7 @@ The line fingerprints go into the existing AI line ledger. At push time, the CLI - Source: [issue-3639-trace-line-attribution D-003](../specs/issue-3639-trace-line-attribution.md) - Code: verified: `app/cli/internal/trace/state/ailines.go`, `app/cli/internal/trace/git/lines.go`. -### ADR-060: Two categories, human by default +### TRC-036: Two categories, human by default A line that the CLI cannot link to an observed agent edit is human. - Why: no change to the evidence categories or the platform aggregates. - Rejected: a third "unknown" category (more exact, but it changes the schema and every aggregate). @@ -302,7 +302,7 @@ A line that the CLI cannot link to an observed agent edit is human. - Source: [issue-3639-trace-line-attribution D-005](../specs/issue-3639-trace-line-attribution.md) - Code: verified: `app/cli/internal/trace/attribution/enrich.go`. -### ADR-061: A human commit with no session joins the sessions it changes +### TRC-037: A human commit with no session joins the sessions it changes At push time, a commit with no session joins each session whose lines it replaced. Only the replacing hunks count, as human lines. - Why: the human work gets credit where it changed the code of the session, and the evidence needs no new field. - Rejected: a new field for the replaced lines of the session (the human lines are in no session total). Count the whole commit in the session (unrelated human work joins the session). @@ -310,7 +310,7 @@ At push time, a commit with no session joins each session whose lines it replace - Source: [issue-3639-trace-line-attribution D-009](../specs/issue-3639-trace-line-attribution.md) - Code: verified: `app/cli/pkg/action/trace_replacements.go`, `app/cli/internal/trace/attribution/enrich.go`. -### ADR-062: Codex uses its native hooks +### TRC-038: Codex uses its native hooks Native Codex hooks connect Codex to the session lifecycle: session start and end, prompts, patches, and shell tools. - Why: they cover the lifecycle with no separate extension. - Rejected: a notification-only integration (misses the state before an edit). A separate extension (duplicates lifecycle support). @@ -318,7 +318,7 @@ Native Codex hooks connect Codex to the session lifecycle: session start and end - Source: [issue-3657-codex-trace-provider D-001](../specs/issue-3657-codex-trace-provider.md) - Code: verified: `app/cli/internal/trace/codex/hooks.go`. -### ADR-063: The Codex provider parses a copy of the rollout +### TRC-039: The Codex provider parses a copy of the rollout The provider keeps a private copy of the native rollout, parses only that copy, and estimates cost from embedded prices. - Why: the evidence stays available after Codex moves or deletes its original transcript. - Rejected: live transcript parsing (depends on agent storage during assembly). Notification payloads (no conversation and usage details). @@ -330,14 +330,14 @@ Not shown: 10 smaller decisions of [issue-3495-session-spec-capture](../specs/is ## Non-Confirmed -### ADR-031: The agent keeps capturing pasted images, and its entry wins +### TRC-019: The agent keeps capturing pasted images, and its entry wins The capture instruction for pasted images stays. When the agent and the CLI capture the same image, one entry is kept for the digest, and the entry of the agent wins. - Why: the agent adds a role, a title, and a description that the CLI cannot know. - Deciders: migmartri, matiasinsaurralde - Source: [issue-3569-pasted-image-pointers D-004](../specs/issue-3569-pasted-image-pointers.md) - Code: not confirmed: the digest rule is built (`app/cli/pkg/action/trace_pasted_images.go`), but the capture instruction is not. See Open conflicts. The parent ticket is done. -### ADR-052: The e2e suite runs nightly on main and by hand, not on pull requests +### TRC-028: The e2e suite runs nightly on main and by hand, not on pull requests - Why: secrets are not available to pull requests from forks, and the cost of a run is high. - Deciders: jiparis, javirln, migmartri - Source: [issue-3635-trace-e2e-matrix D-011](../specs/issue-3635-trace-e2e-matrix.md) @@ -349,5 +349,5 @@ None. ## Open conflicts -- ADR-007 ([issue-3515-spec-capture-during-session D-009](../specs/issue-3515-spec-capture-during-session.md)) and ADR-031 ([issue-3569-pasted-image-pointers D-004](../specs/issue-3569-pasted-image-pointers.md)) disagree about the agent capture of pasted images, and neither has a "replaces" note. The instructions follow ADR-007 (`app/cli/pkg/action/trace_spec.go`). The CLI captures pasted images itself for Claude Code and Codex (ADR-030). For Cursor, OpenCode, and Pi, nothing records a pasted image as a source. Proposed: write a spec change that records the current rule. -- ADR-052: the parent ticket is done, but the nightly job does not exist. Proposed: write a spec change that drops the nightly run, so the suite stays manual. +- TRC-003 ([issue-3515-spec-capture-during-session D-009](../specs/issue-3515-spec-capture-during-session.md)) and TRC-019 ([issue-3569-pasted-image-pointers D-004](../specs/issue-3569-pasted-image-pointers.md)) disagree about the agent capture of pasted images, and neither has a "replaces" note. The instructions follow TRC-003 (`app/cli/pkg/action/trace_spec.go`). The CLI captures pasted images itself for Claude Code and Codex (TRC-018). For Cursor, OpenCode, and Pi, nothing records a pasted image as a source. Proposed: write a spec change that records the current rule. +- TRC-028: the parent ticket is done, but the nightly job does not exist. Proposed: write a spec change that drops the nightly run, so the suite stays manual. diff --git a/docs/decisions/service-operations.md b/docs/decisions/service-operations.md index fbedafebb..19e7b8d17 100644 --- a/docs/decisions/service-operations.md +++ b/docs/decisions/service-operations.md @@ -3,7 +3,7 @@ AGENT INSTRUCTIONS. Keep this comment. Read it before you change this file. The br:eng-adr skill writes this page. Do not edit it by hand, and do not change it as part of other work. A person approves each change before the skill writes it, and the area owner reviews it in a PR. -Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### ADR-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. +Structure. The index (README.md) has the parts Areas (the area map), Relationships, Repository-wide decisions, Non-Confirmed, Superseded, and Open conflicts. An area page has the parts Language, Current decisions, Non-Confirmed, Superseded, and Open conflicts. Each decision is a "### <PFX>-NNN: <title>" entry with the choice, then the lines Why, Rejected, Deciders, Source, and Code. - Current decisions and Repository-wide decisions: the rules that hold today. Follow them. The page holds only choices that a reader of the code would likely get wrong. It does not describe how the system works: for that, read the code or the specs. - Non-Confirmed: decisions that the code does not show yet, for example a plan in a spec. They are not rules yet. - Superseded: old decisions, struck through, each with the ID of the decision that replaced it. They are history only. @@ -12,9 +12,9 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi - Before you change the behavior of an area, read its current decisions and its Language. The page leaves out smaller decisions (the "Not shown" line), so also check the Decision Records of the specs that the page lists. Read the linked spec for the reasons. - To record a decision that the team made and built: run br:eng-adr new. -- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces ADR-NNN". +- To change a decision, or to make one that is not built yet: write a spec with br:eng-spec. Its Decision Record entry says "replaces <PFX>-NNN". - To refresh this page from the specs and the code: run br:eng-adr reconcile. -- Never renumber or reuse an ADR-NNN ID. Never delete a decision: supersede it. +- Never renumber or reuse a decision ID. Never delete a decision: supersede it. - When your code goes against a current decision, say so in your PR. Do not edit this page to match the code. --> @@ -31,14 +31,14 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ## Current decisions -### ADR-045: Metrics and profiler ports stay separate +### OPS-002: Metrics and profiler ports stay separate The metrics and profiler endpoints keep their own ports. They are not on the shared endpoint. - Why: they are internal and must not be on the public hostname. - Deciders: migmartri, matiasinsaurralde - Source: [issue-3628-single-endpoint D-006](../specs/issue-3628-single-endpoint.md) - Code: verified: `app/artifact-cas/internal/server/httpmetrics.go` (separate metrics server), `app/controlplane/internal/server/profiler.go` (pprof on its own port). -### ADR-046: TLS covers all protocols on the shared endpoint +### OPS-003: TLS covers all protocols on the shared endpoint When the operator configures TLS, all protocols on the shared endpoint use it, and an HTTP ingress must use an HTTPS backend. The legacy gRPC listener keeps its own TLS settings during the deprecation period. - Why: one certificate and one setting for each service. - Rejected: plain text and TLS on the same port (a second classification step, and a weaker TLS setting). @@ -46,7 +46,7 @@ When the operator configures TLS, all protocols on the shared endpoint use it, a - Source: [issue-3628-single-endpoint D-008](../specs/issue-3628-single-endpoint.md) - Code: verified: `app/artifact-cas/internal/server/http.go`. The legacy listener keeps `server.grpc.tls_config` (`deployment/chainloop/templates/cas/configmap.yaml`). -### ADR-047: The current HTTP port is the shared endpoint +### OPS-004: The current HTTP port is the shared endpoint The HTTP port of each service becomes the shared endpoint. The gRPC port is the legacy listener. - Why: the human-facing hostname, with the status, download, and login endpoints, is already on the HTTP port. Clients that use ingress hostnames do not break in either direction. - Rejected: the gRPC port (TLS settings stay in place, but the human-facing hostname stays on a second port). A new port (every client changes). @@ -54,7 +54,7 @@ The HTTP port of each service becomes the shared endpoint. The gRPC port is the - Source: [issue-3628-single-endpoint D-012](../specs/issue-3628-single-endpoint.md) - Code: verified: `app/artifact-cas/internal/server/http.go`, `app/artifact-cas/internal/server/grpc.go` (legacy listener on by default). -### ADR-048: The server process does the multiplexing, with no proxy or sidecar +### OPS-005: The server process does the multiplexing, with no proxy or sidecar Each service serves all protocols on its shared endpoint in its own process. It runs on a VM, in Docker Compose, and in Kubernetes with no proxy or sidecar. - Why: no extra process, configuration, or lifecycle in any deployment. - Rejected: a chart-level proxy or Gateway API routes (extra infrastructure, and two ports stay). A mandatory sidecar or embedded proxy (a process and its lifecycle in every deployment). @@ -67,7 +67,7 @@ Not shown: 15 smaller or replaced decisions of [issue-3628-single-endpoint](../s ## Non-Confirmed -### ADR-044: The CAS keeps its native gRPC transport and separate endpoints +### OPS-001: The CAS keeps its native gRPC transport and separate endpoints - Why: a paused-reader probe showed that the HTTP adapter can read ahead faster than the application receives. - Deciders: migmartri, jiparis - Source: [issue-3628-controlplane-http-dispatch D-003](../specs/issue-3628-controlplane-http-dispatch.md) @@ -79,4 +79,4 @@ None. ## Open conflicts -- ADR-044 ([issue-3628-controlplane-http-dispatch D-003](../specs/issue-3628-controlplane-http-dispatch.md)) and [issue-3628-single-endpoint D-020](../specs/issue-3628-single-endpoint.md) disagree about the CAS transport, and neither has a "replaces" note. The code follows D-020. Proposed: write a spec change that records D-020 as the replacement of dispatch D-003. +- OPS-001 ([issue-3628-controlplane-http-dispatch D-003](../specs/issue-3628-controlplane-http-dispatch.md)) and [issue-3628-single-endpoint D-020](../specs/issue-3628-single-endpoint.md) disagree about the CAS transport, and neither has a "replaces" note. The code follows D-020. Proposed: write a spec change that records D-020 as the replacement of dispatch D-003. From bf7e598cbd2f84410e49fb23a40fe802ea2e7dfd Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino <miguel@chainloop.dev> Date: Tue, 3 Nov 2026 22:30:10 +0100 Subject: [PATCH 3/6] docs(decisions): check the written text against the specs and the code Add the conditions and limits that the code shows (defaults, opt-in settings, error paths, filters, and scope) to the entries and the Language of each area. Move ATT-002 to Current decisions, narrow the OPS-001 conflict, and add an Authentication conflict about the download login. Point CLAUDE.md agents to the decisions index. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: e3100522-144f-4450-91f7-eed1cd2c29d9 --- CLAUDE.md | 2 +- docs/decisions/README.md | 2 +- docs/decisions/artifact-cas.md | 6 +-- docs/decisions/attestation-process.md | 19 +++++---- docs/decisions/authentication.md | 8 ++-- docs/decisions/chainloop-trace.md | 60 +++++++++++++++++---------- docs/decisions/service-operations.md | 30 +++++++------- 7 files changed, 72 insertions(+), 55 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 438ab6d05..cf45331d9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ Chainloop is an open-source evidence store for Software Supply Chain attestation ## Design Specs -Design specs live in `docs/specs/`. Each one records an intended design and its reasons. Read the relevant spec before you change that behavior. Cite requirement IDs (for example R-001) in PRs that implement them. When a change goes against an implemented spec, say so in the PR, and propose a new spec for a large change. Do not edit an implemented spec's design. Never put customer or user information in a spec. Parent tickets for specs live in the GitHub issues of this repository. Create and update specs with `br:eng-spec`, never by hand-rolling a new format. +Design specs live in `docs/specs/`. Each one records a design and its reasons at the time it was built. Before you change the behavior of an area, find it in the decisions index [`docs/decisions/README.md`](docs/decisions/README.md), if there is one. Then read its Current Decisions page. It shows the decisions and the words that hold today. `br:eng-adr` writes those pages: do not edit them by hand. Read the relevant spec for the reasons before you change that behavior. Cite requirement IDs (for example R-001) in PRs that implement them. While the ticket is in progress and the spec is on the default branch, supersede changed entries in the implementation PR. Sometimes the spec and its implementation are in the same PR, and the spec is not on the default branch yet. Then change the spec directly so that it matches the implementation. The agent instructions comment of the spec has the details. When a change goes against an implemented spec, say so in the PR, and propose a new spec for a large change. Do not edit an implemented spec's design. Never put customer or user information in a spec. Parent tickets for specs live in the GitHub issues of this repository. Create and update specs with `br:eng-spec`, never by hand-rolling a new format. ## Architecture diff --git a/docs/decisions/README.md b/docs/decisions/README.md index cc8a9bee7..73e81cbbb 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -75,7 +75,7 @@ Last refresh: 2026-11-01, commit 4e150ca7. - **Attestation Process → Chainloop Trace:** both read `.chainloop.yml` through one reader (ATT-003, attestation-process.md). A change to the file order or keys changes both areas. - **Chainloop Trace → Artifact CAS:** Trace uploads each spec source, skill package, and pasted image as its own material. A transcript pointer replaces a copy only when its material is stored (TRC-016, chainloop-trace.md). -- **Service Operations → Artifact CAS, Authentication:** the shared endpoint carries the CAS ByteStream and download routes and the login endpoints. A change to the dispatch or TLS affects all of them. +- **Service Operations → Artifact CAS, Authentication:** the shared endpoints carry the CAS ByteStream and download routes and the control plane login endpoints. A change to their dispatch or TLS affects all of them. It does not affect ByteStream traffic on the legacy gRPC listener, which the chart uses by default for the control plane. ## Repository-wide decisions diff --git a/docs/decisions/artifact-cas.md b/docs/decisions/artifact-cas.md index 818cf082d..63304d16d 100644 --- a/docs/decisions/artifact-cas.md +++ b/docs/decisions/artifact-cas.md @@ -32,7 +32,7 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ## Current decisions ### CAS-001: Existence cache in NATS KV when connected, in memory otherwise -The existence cache uses the shared cache library. It uses NATS KV when the CAS has a NATS connection, and an in-memory LRU when it does not. +The existence cache uses the shared cache library. It uses NATS KV when the CAS has a NATS connection. It uses an in-memory LRU when it has no connection, or when the NATS bucket cannot be set up at startup. A NATS failure at runtime is a cache miss, not a change to memory. - Why: replicas share hits when NATS is there, and the CAS gets no new dependency when it is not. - Rejected: memory only (the hit rate drops as replicas grow). NATS required (a hard dependency for a speed gain). - Deciders: migmartri, javirln @@ -71,12 +71,12 @@ The CAS sets cache headers on downloads and answers 304 when the browser has the - Code: verified: `app/artifact-cas/internal/service/download.go`. ### CAS-006: The download token goes in the Authorization header -The CAS accepts the download token in an `Authorization: Bearer` header, so the download URL stays the same between calls. +The CAS accepts the download token in an `Authorization: Bearer` header. When a client moves the token from the `?t=` query to this header, the download URL stays the same between calls. - Why: the browser cache uses the URL as its key, and the token changes on each call. - Rejected: one token for each user and digest (the URL stays the same only for the life of the token). - Deciders: jiparis, migmartri - Source: [issue-3549-cas-download-browser-cache D-003](../specs/issue-3549-cas-download-browser-cache.md) -- Code: verified on the CAS side: `pkg/middlewares/http/jwt.go`, CORS allows `Authorization`. The control plane `GetDownloadURL` still puts the token in `?t=` (`app/controlplane/internal/service/casredirect.go`). The client that moves the token to the header is not in this repository. +- Code: verified on the CAS side: `pkg/middlewares/http/jwt.go`. When `cors.allow_origins` is set, the preflight in `app/artifact-cas/internal/server/cors.go` allows `Authorization`. The control plane `GetDownloadURL` still puts the token in `?t=` (`app/controlplane/internal/service/casredirect.go`). The client that moves the token to the header is not in this repository. ### CAS-007: The header token wins, with no fallback to the query When a request has a token in the header and in the query, the CAS uses only the header. A bad header fails with 401. diff --git a/docs/decisions/attestation-process.md b/docs/decisions/attestation-process.md index db28effc8..fd8a25a6c 100644 --- a/docs/decisions/attestation-process.md +++ b/docs/decisions/attestation-process.md @@ -26,18 +26,25 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ## Language -- **Repository config**: the `.chainloop.yml` file at the repository root that pins `organization` and `projectName` for the repository. _Avoid_: "workflow file", because the file holds no workflow. +- **Repository config**: the nearest `.chainloop.yml` (or `.chainloop.yaml`), from the current directory up to the repository root. It pins `organization` and `projectName`. _Avoid_: "workflow file", because the attestation commands do not read a workflow from it. - **Saved default**: the organization that the CLI keeps in its own configuration for the user. _Avoid_: "default org" without a qualifier, because the repository config can also give an organization. ## Current decisions ### ATT-001: Organization order is flag, environment variable, repository config, saved default -The `--org` flag and its environment variable win. The repository config applies only when neither is set, and it comes before the saved default. +The `--org` flag and its environment variable win. For `init`, `add`, `push`, `status`, and `reset`, the repository config applies only when neither is set, and it comes before the saved default. - Why: the file pins the organization for the repository. - Rejected: the saved default before the file (most users have a saved default, so the file would almost never apply). - Deciders: migmartri, javirln - Source: [issue-3063-attestation-repo-config D-002](../specs/issue-3063-attestation-repo-config.md) -- Code: verified: `app/cli/cmd/attestation.go`, `app/cli/cmd/root.go`. For a command other than init with no remote state, the organization of the local attestation state also comes before the saved default (`attestation.go`). +- Code: verified: `app/cli/cmd/attestation.go`, `app/cli/cmd/root.go`. For a command other than init, when the repository config gives no organization, the organization of the local attestation state comes before the saved default (`attestation.go`). + +### ATT-002: init, add, push, status, and reset read the organization from the file +The attestation commands that contact the control plane read the organization from the repository config. They are `init`, `add`, `push`, `status`, and `reset`. +- Why: with remote state, a command must connect to the correct organization before it can load the state. +- Deciders: migmartri, javirln +- Source: [issue-3063-attestation-repo-config D-004](../specs/issue-3063-attestation-repo-config.md) +- Code: verified: `app/cli/cmd/attestation.go`. `attestation verify` does not read the file, and the spec does not ask it to. ### ATT-003: One reader of `.chainloop.yml` for attestation and trace Attestation commands and Trace read the repository config through one package. @@ -59,11 +66,7 @@ Not shown: 6 smaller decisions of [issue-3063-attestation-repo-config](../specs/ ## Non-Confirmed -### ATT-002: All attestation commands that contact the control plane read the organization from the file -- Why: with remote state, a command must connect to the correct organization before it can load the state. -- Deciders: migmartri, javirln -- Source: [issue-3063-attestation-repo-config D-004](../specs/issue-3063-attestation-repo-config.md) -- Code: not confirmed: init, add, push, status, and reset read the file (`app/cli/cmd/attestation.go`). `attestation verify` contacts the control plane (`app/cli/pkg/action/attestation_verify.go`) and does not read the file. The parent ticket is open. +None. ## Superseded diff --git a/docs/decisions/authentication.md b/docs/decisions/authentication.md index f9f6775ae..3f474d9fa 100644 --- a/docs/decisions/authentication.md +++ b/docs/decisions/authentication.md @@ -55,15 +55,15 @@ The table holds the code hash, the user, the expiry, and the challenge. One cond - Source: [issue-3626-login-code-exchange D-004](../specs/issue-3626-login-code-exchange.md) - Code: verified: `app/controlplane/pkg/data/loginhandoff.go`, `app/controlplane/internal/service/auth.go`. -### AUT-004: Opt-in per request, with flags on the info endpoint -The server issues a code only when the request has a challenge. The info endpoint reports whether the exchange is supported and whether the legacy callback is allowed. +### AUT-004: Opt-in per request for client callbacks, with flags on the info endpoint +For a client callback, the server issues a code only when the request has a challenge. A download callback (`/download/<digest>`) must send a challenge. The info endpoint reports whether the exchange is supported and whether the legacy callback is allowed. Legacy is allowed unless the operator sets `allow_legacy_login_callback: false`. - Why: old CLIs need no change. A new CLI knows what to expect and can detect a downgrade. - Rejected: opt-in with no flag (the CLI cannot tell a downgrade from an old server). A version check (fails with forks and development builds). - Deciders: migmartri, jiparis - Source: [issue-3626-login-code-exchange D-005](../specs/issue-3626-login-code-exchange.md) - Code: verified: `app/controlplane/api/controlplane/v1/status.proto`, `app/controlplane/internal/service/status.go`, `app/cli/cmd/auth_login.go`. -### AUT-005: The CLI accepts `?t=` only while the server allows the legacy callback +### AUT-005: After it sends a challenge, the CLI accepts `?t=` only while the server allows the legacy callback When the CLI sent a challenge and gets `?t=`, it accepts the token with a warning while the server reports legacy allowed. It refuses the token when legacy is denied. - Why: a login page that does not forward the new parameters yet must not break login. The loopback listener has a random port and a short life, so the fixation risk is low. - Rejected: refuse `?t=` from the first CLI release (it breaks login behind such a login page). @@ -99,4 +99,4 @@ None. ## Open conflicts -None. +- AUT-004 and [issue-3626-login-code-exchange D-008](../specs/issue-3626-login-code-exchange.md) disagree about the download login. D-008 keeps it on the legacy callback. The code requires a challenge for a `/download/` callback (`app/controlplane/internal/service/auth.go`). Proposed: write a spec change that records the current download flow. diff --git a/docs/decisions/chainloop-trace.md b/docs/decisions/chainloop-trace.md index 9f6830dbf..9656e7917 100644 --- a/docs/decisions/chainloop-trace.md +++ b/docs/decisions/chainloop-trace.md @@ -26,11 +26,11 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ## Language -- **Provider**: the Trace integration for one AI coding agent (Claude Code, Cursor, OpenCode, Pi, Codex). _Avoid_: "harness" in code and evidence. The e2e suite uses "harness" for the agent CLI that it runs. +- **Provider**: the Trace integration for one AI coding agent (Claude Code, Cursor, OpenCode, Pi, Codex). _Avoid_: "harness" in new code and in evidence. The e2e suite and the `trace init` help text and output still use "harness" for the agent. - **Spec source**: an input that the session was built from (a ticket, a document, a plan, an image, a skill). It is recorded as a spec entry and its own material. _Avoid_: "attachment", because a source is evidence with a digest, not a file on a message. -- **Capture**: a file that the agent writes into the spec folder for one source. _Avoid_: "export", because the agent writes the capture, not the CLI. +- **Capture**: a file that the agent writes into the spec folder for one source. The CLI also captures pasted images (TRC-018). _Avoid_: "export", which was the old name of local mode. - **Pointer**: the `chainloop.replaced` object that takes the place of a copy of a spec source in the transcript. _Avoid_: "placeholder", which is the fixed line in a local-file capture. -- **Local mode**: the storage mode (`traceStorage: LOCAL`) that stores sessions on disk with no control plane. _Avoid_: "offline", because the agent and the model are still online. Also avoid "export mode". +- **Local mode**: the storage mode (`traceStorage: LOCAL`, or `trace run --local` for one run) that stores sessions on disk with no control plane. _Avoid_: "offline", because the agent and the model are still online. Also avoid "export mode". - **Net contribution**: the lines that a session added and that no later commit replaced. _Avoid_: "authored lines", which counts lines that later commits replaced. ## Current decisions @@ -38,14 +38,14 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ### TRC-001: The agent decides what the spec is An instruction at session start tells the agent to resolve the spec of the session and write it into the spec folder. - Why: only the agent knows which input sets the task, and it reaches sources that the transcript never holds. -- Rejected: extraction from the transcript by the CLI. It needs rules to pick the spec, misses sources that the agent never opened, and needs one extractor for each agent. Extraction on the server after ingestion (the spec is not in the attestation). +- Rejected: extraction from the transcript by the CLI. It needs rules to pick the spec, misses sources that the agent never opened, and needs one extractor for each agent. Extraction on the server after ingestion (the spec is not in the attestation). Both stay as a fallback if the capture rate is poor. - Deciders: jiparis, migmartri - Source: [issue-3495-session-spec-capture D-001](../specs/issue-3495-session-spec-capture.md) - Code: verified: `app/cli/pkg/action/trace_spec.go`, `app/cli/pkg/action/trace_agent_hook.go`. -- Partly replaced by TRC-009: the CLI makes the skill entries. +- Partly replaced by TRC-009 and TRC-018: the CLI makes the skill entries and the pasted image entries. ### TRC-002: A capture is Markdown with a short header -A capture is a Markdown body under a frontmatter header (kind, uri, role, title, description). With no header or a bad header, the whole file is content. An unknown kind becomes `text`. +A text capture is a Markdown body under a frontmatter header (kind, uri, role, title, description). The CLI copies a binary file as it is, with no header. With no header or an unterminated header, the whole file is content. The CLI skips a header line with an unknown key. An unknown kind becomes `text`. A file with an empty body or only the placeholder gives no capture. - Why: a model writes long Markdown reliably. Long text escaped inside JSON fails completely when one character is wrong. A bad header still keeps the content. - Rejected: JSON files. - Deciders: jiparis, migmartri @@ -92,7 +92,7 @@ For a binary file, the agent writes `<name>.meta.yaml` next to it, with the role - Code: verified: `app/cli/internal/trace/spec/spec.go`, `app/cli/pkg/action/trace_spec_materials.go`. ### TRC-008: A used skill is a spec entry of kind `skill` -Each skill that the session used is an entry of kind `skill` in the spec list of the session. +Each skill that the session used is an entry of kind `skill` in the spec list of the session, up to 25 skills. A skill with no folder gets no entry. This applies to the providers with skill tracking. - Why: a skill is a source of the session, like a ticket or a plan. It gets the same storage, redaction, and annotations. - Rejected: a separate `skills_used` list in the session material. - Deciders: jiparis, migmartri @@ -107,7 +107,7 @@ The CLI finds skill use in the transcript and the hook events. The agent does no - Code: verified for Claude Code, Codex, and OpenCode: `app/cli/internal/trace/claude/skills.go`, `app/cli/pkg/action/trace_skills.go`. Cursor and Pi have no skill tracking. ### TRC-010: The whole skill folder is recorded -The CLI copies the full folder of a used skill, not only the text that the agent loaded. +The CLI copies the folder of a used skill, not only the text that the agent loaded. It leaves out links out of the folder and version-control folders. - Why: a skill can run its own scripts, and a reviewer needs that code. The attestation must not depend on the `ai-agent-config` material. - Rejected: the `SKILL.md` text from the transcript (no scripts, and Claude Code deletes the frontmatter). - Deciders: jiparis, migmartri @@ -115,7 +115,7 @@ The CLI copies the full folder of a used skill, not only the text that the agent - Code: verified: `app/cli/pkg/action/trace_skills.go`, `app/cli/internal/trace/skill/skill.go`. ### TRC-011: One reproducible archive for each skill -Each skill gives one archive with fixed file times, stored as one package material. +Each skill gives one archive with fixed file times, stored as one package material. The CLI does not upload a package over 5 MB, and shows a warning. The entry and its counts stay. - Why: one package digest for each skill, and the same skill version gives the same digest in all sessions. - Rejected: one material for each file (it fills the attestation for a skill with many files). - Deciders: jiparis, migmartri @@ -123,7 +123,7 @@ Each skill gives one archive with fixed file times, stored as one package materi - Code: verified: `app/cli/internal/trace/skill/skill.go`, `app/cli/pkg/action/trace_skills.go`. ### TRC-012: Copies of a source become digest pointers in the transcript -The content stays in the spec material. Each copy in the transcript is replaced in place by a pointer with the digest. +The content stays in the spec material. The CLI replaces each exact copy that it finds in the transcript, in place, with a pointer with the digest. A copy that does not match stays inline. - Why: the transcript gets small and keeps the position of each copy. The content stays in the evidence. - Rejected: drop the copies (a reader loses the position). Delete only the second copy of read files (the transcript is still large, and the redaction still scans the bytes). - Deciders: migmartri, javirln @@ -155,7 +155,7 @@ Each kind of copy (file read, file write, pasted image) has its own finder, and - Code: verified: `app/cli/internal/trace/pointer/pointer.go`. ### TRC-016: Replace a copy only on an exact digest match -The CLI replaces a copy only when the material is stored and its digest equals the digest of the copy. +The CLI replaces a copy only when two conditions hold. The source is stored as a material. The digest of the source file on disk equals the digest of the copy. For a file read, the CLI uses the digest of the file at the read path. - Why: the transcript is evidence, and a wrong pointer loses content with no way back. - Rejected: a best-effort match, for example by size or file name (a wrong match deletes content that is stored nowhere else). - Deciders: migmartri, javirln @@ -171,7 +171,7 @@ The replacement covers each exact copy of a source in the spec list, from file r - Code: verified: `app/cli/internal/trace/claude/speccopies.go`, `app/cli/pkg/action/trace_spec_materials.go`. ### TRC-018: The CLI captures pasted images at push time -At push time, the CLI makes an image spec entry for each pasted image in the transcript, then replaces its inline copy with a pointer. +At push time, the CLI makes an image spec entry for each distinct pasted image whose digest is not already a spec source. When the material is stored, the CLI replaces the inline copies with a pointer. - Why: the bytes are in the transcript, so the capture depends on neither the agent nor a file on disk. - Rejected: replace only the images that the agent captured. Images then stay inline when the agent does not copy them, and the agent can copy only from a file path. - Deciders: migmartri, matiasinsaurralde @@ -219,7 +219,7 @@ A stored session has `statement.json`, rendered by the same Chainloop renderer a - Code: verified: `app/cli/pkg/action/trace_local_store.go`. ### TRC-025: The e2e suite runs the real agent CLI, headless, with a real model -- Why: only a real run shows whether the agent fires the hooks, shows the message, and follows the instruction. +- Why: only a real run shows whether the agent fires the hooks and shows the message, and whether the model follows the instruction. - Rejected: replay recorded hook events into Trace (cheap and stable, but blind to the agent and the model). Both tiers (the replay tier adds little over the unit tests). - Deciders: jiparis, javirln, migmartri - Source: [issue-3635-trace-e2e-matrix D-001](../specs/issue-3635-trace-e2e-matrix.md) @@ -241,7 +241,7 @@ The e2e suite reads the feature tables in the Trace README as its expected resul - Code: verified: `app/cli/internal/trace/e2e/workspace.go`, `app/cli/internal/trace/e2e/runner.go`. ### TRC-029: Agent versions are pinned and bumped by hand -The suite configuration pins one version for each agent. A maintainer bumps a pin on purpose. +The suite configuration pins one version for each agent that the suite runs (Claude Code, Cursor, OpenCode 2, Pi, Codex). OpenCode 1.x has no pin and no adapter. A maintainer bumps a pin on purpose. - Why: the job has access to secrets and must not run an agent release that nobody selected. - Rejected: the latest release on each run (runs code that nobody selected in a job with secrets). Automated bump pull requests (more tooling for the first version). - Deciders: jiparis, javirln, migmartri @@ -249,7 +249,7 @@ The suite configuration pins one version for each agent. A maintainer bumps a pi - Code: verified: `app/cli/internal/trace/e2e/config.yaml`. The suite does not install the agent. It warns when the installed version is not the pinned one. ### TRC-030: Each line range gets an attribution in the existing list -The ranges stay in the one list of each file entry, and each range gets an attribution, AI or human. +The ranges stay in the one list of each file entry, and each range gets an attribution, AI or human. When the CLI cannot blame a file at the pushed commit, that file keeps its AI ranges at edit time, with no attribution. - Why: one list keeps the evidence small and gives readers one place to look. The new property is additive. - Rejected: a new list of AI ranges next to the old one (two lists for the same lines). Two new lists for AI and human (a reader must merge them to show a file). - Deciders: jiparis, migmartri @@ -257,6 +257,7 @@ The ranges stay in the one list of each file entry, and each range gets an attri - Code: verified: `pkg/attestation/crafter/materials/aicodingsession/aicodingsession.go`, `app/cli/internal/trace/attribution/enrich.go`. ### TRC-031: Range positions are in the pushed commit +Ranges are positions at the pushed commit. When the CLI cannot blame a file at the pushed commit, that file keeps its AI ranges at edit time, with no attribution. - Why: the lines that later commits replaced are gone at that commit, so the ranges agree with the net counts. A reader of the pull request sees the same version. - Rejected: the last commit of the session (a later commit can move or replace the lines). Ranges for each commit (a reader must combine them). - Deciders: jiparis, migmartri @@ -264,14 +265,14 @@ The ranges stay in the one list of each file entry, and each range gets an attri - Code: verified: `app/cli/internal/trace/attribution/enrich.go`. ### TRC-032: A range with no attribution is an AI range -Evidence from an older CLI has ranges with no attribution. Readers treat these as AI ranges at edit time. +Evidence from an older CLI has ranges with no attribution. A file that the CLI cannot blame at the pushed commit also has them. Readers treat these as AI ranges at edit time. - Why: platforms that store evidence from older CLIs must read both forms. - Deciders: jiparis, migmartri - Source: [issue-3639-line-ranges D-004](../specs/issue-3639-line-ranges.md) - Code: verified as a documented contract: `pkg/attestation/crafter/materials/aicodingsession/aicodingsession.go`. No reader is in this repository. ### TRC-033: The reported split is the net contribution -The AI and human numbers for each file and session are the lines that the session added and that no later commit replaced. +The AI and human numbers for each file and session are the lines that the session added and that no later commit replaced. When the CLI cannot blame a file at the pushed commit, its numbers are the sum of its commits. A file with edits from before line fingerprints keeps the earlier rule. - Why: it gives the expected split, and no line counts twice across commits. - Rejected: authored lines for each commit, with no net (a replaced AI line still counts as AI). - Deciders: jiparis, migmartri @@ -279,7 +280,7 @@ The AI and human numbers for each file and session are the lines that the sessio - Code: verified: `app/cli/internal/trace/attribution/enrich.go`. ### TRC-034: A replacement sends the owning session again -When a later commit replaces lines of a session, the CLI sends the evidence of that session again. +When a later commit replaces lines of a session, the next push sends the evidence of that session again. This occurs only when the own commits of the session are on the pushed branch, and for 30 days after the session starts (D-012, D-013). - Why: the platform already replaces session data with newer evidence, so it needs no new concept. - Rejected: separate evidence with no session for human commits (a new concept on the platform). No tracking of commits outside sessions (the human work stays uncounted). - Deciders: jiparis, migmartri @@ -287,7 +288,7 @@ When a later commit replaces lines of a session, the CLI sends the evidence of t - Code: verified: `app/cli/pkg/action/trace_replacements.go`, `app/cli/pkg/action/trace_hook_handler.go`. ### TRC-035: Line owners are rebuilt from blame and the ledger -The line fingerprints go into the existing AI line ledger. At push time, the CLI rebuilds the owner of each line from git blame, the commit records, and the ledger. It stores no owner data for each line. +The line fingerprints go into the existing AI line ledger. At push time, the CLI rebuilds the owner of each line from git blame, the commit records, and the ledger. It stores no owner data for each line. The exception is the record of a commit that joined a session: it keeps the fingerprints of the replacing lines. - Why: it reuses the existing storage and pushes nothing to the remote. No second store can disagree with it. - Rejected: git notes on a dedicated ref (push traffic, ref contention, and git configuration). An owner map for each commit (it duplicates the ledger and needs its own rewrite handling). - Deciders: jiparis, migmartri @@ -295,7 +296,7 @@ The line fingerprints go into the existing AI line ledger. At push time, the CLI - Code: verified: `app/cli/internal/trace/state/ailines.go`, `app/cli/internal/trace/git/lines.go`. ### TRC-036: Two categories, human by default -A line that the CLI cannot link to an observed agent edit is human. +A line that the CLI cannot link to an observed agent edit is human. Exception: for a file with edits from before line fingerprints, all deleted lines count as AI. - Why: no change to the evidence categories or the platform aggregates. - Rejected: a third "unknown" category (more exact, but it changes the schema and every aggregate). - Deciders: jiparis, migmartri @@ -303,7 +304,7 @@ A line that the CLI cannot link to an observed agent edit is human. - Code: verified: `app/cli/internal/trace/attribution/enrich.go`. ### TRC-037: A human commit with no session joins the sessions it changes -At push time, a commit with no session joins each session whose lines it replaced. Only the replacing hunks count, as human lines. +At push time, a commit with no session joins each session whose lines it replaced. Only the replacing hunks count, as human lines. This applies only to sessions whose own commits are on the pushed branch, within the 30 days of D-012. - Why: the human work gets credit where it changed the code of the session, and the evidence needs no new field. - Rejected: a new field for the replaced lines of the session (the human lines are in no session total). Count the whole commit in the session (unrelated human work joins the session). - Deciders: jiparis, migmartri @@ -319,14 +320,27 @@ Native Codex hooks connect Codex to the session lifecycle: session start and end - Code: verified: `app/cli/internal/trace/codex/hooks.go`. ### TRC-039: The Codex provider parses a copy of the rollout -The provider keeps a private copy of the native rollout, parses only that copy, and estimates cost from embedded prices. +The provider keeps a private copy of the native rollout. The evidence parser reads only that copy, and estimates cost from embedded prices. During the session, a hook reads the live rollout only to find skill loads. - Why: the evidence stays available after Codex moves or deletes its original transcript. - Rejected: live transcript parsing (depends on agent storage during assembly). Notification payloads (no conversation and usage details). - Deciders: jiparis, migmartri, matiasinsaurralde - Source: [issue-3657-codex-trace-provider D-003](../specs/issue-3657-codex-trace-provider.md) - Code: verified: `app/cli/internal/trace/codex/copy.go`, `app/cli/internal/trace/codex/parse.go`, `app/cli/internal/trace/codex/pricing.go`. -Not shown: 10 smaller decisions of [issue-3495-session-spec-capture](../specs/issue-3495-session-spec-capture.md), 8 of [issue-3515-spec-capture-during-session](../specs/issue-3515-spec-capture-during-session.md), 12 of [issue-3520-pi-trace-provider](../specs/issue-3520-pi-trace-provider.md), 10 of [issue-3531-spec-source-role](../specs/issue-3531-spec-source-role.md), 12 of [issue-3540-trace-skills-used](../specs/issue-3540-trace-skills-used.md), 10 of [issue-3556-session-image-placeholders](../specs/issue-3556-session-image-placeholders.md), 10 of [issue-3561-up-to-date-local-spec-evidence](../specs/issue-3561-up-to-date-local-spec-evidence.md), 5 of [issue-3569-pasted-image-pointers](../specs/issue-3569-pasted-image-pointers.md), 20 of [issue-3582-trace-evidence-export](../specs/issue-3582-trace-evidence-export.md), 8 of [issue-3635-trace-e2e-matrix](../specs/issue-3635-trace-e2e-matrix.md), 3 of [issue-3639-line-ranges](../specs/issue-3639-line-ranges.md), 8 of [issue-3639-trace-line-attribution](../specs/issue-3639-trace-line-attribution.md), 2 of [issue-3657-codex-trace-provider](../specs/issue-3657-codex-trace-provider.md). +Not shown: the smaller decisions of each spec. +- 10 of [issue-3495-session-spec-capture](../specs/issue-3495-session-spec-capture.md) +- 8 of [issue-3515-spec-capture-during-session](../specs/issue-3515-spec-capture-during-session.md) +- 12 of [issue-3520-pi-trace-provider](../specs/issue-3520-pi-trace-provider.md) +- 10 of [issue-3531-spec-source-role](../specs/issue-3531-spec-source-role.md) +- 12 of [issue-3540-trace-skills-used](../specs/issue-3540-trace-skills-used.md) +- 10 of [issue-3556-session-image-placeholders](../specs/issue-3556-session-image-placeholders.md) +- 10 of [issue-3561-up-to-date-local-spec-evidence](../specs/issue-3561-up-to-date-local-spec-evidence.md) +- 5 of [issue-3569-pasted-image-pointers](../specs/issue-3569-pasted-image-pointers.md) +- 20 of [issue-3582-trace-evidence-export](../specs/issue-3582-trace-evidence-export.md) +- 8 of [issue-3635-trace-e2e-matrix](../specs/issue-3635-trace-e2e-matrix.md) +- 3 of [issue-3639-line-ranges](../specs/issue-3639-line-ranges.md) +- 8 of [issue-3639-trace-line-attribution](../specs/issue-3639-trace-line-attribution.md) +- 2 of [issue-3657-codex-trace-provider](../specs/issue-3657-codex-trace-provider.md) ## Non-Confirmed diff --git a/docs/decisions/service-operations.md b/docs/decisions/service-operations.md index 19e7b8d17..26990270d 100644 --- a/docs/decisions/service-operations.md +++ b/docs/decisions/service-operations.md @@ -26,42 +26,42 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ## Language -- **Shared endpoint**: the HTTP port of a service (`server.http.addr`). It serves native gRPC, plain HTTP, and, on the control plane, gRPC-Web. _Avoid_: "single port" for the legacy listener, and "gRPC port" for the shared endpoint. -- **Legacy gRPC listener**: the separate native gRPC port that stays during the deprecation period. _Avoid_: "the gRPC endpoint", because the shared endpoint also serves gRPC. +- **Shared endpoint**: the HTTP port of a service (`server.http.addr`). It serves native gRPC, plain HTTP, and, on the control plane, gRPC-Web. _Avoid_: "single port", the older word for it, because the service still has other ports. Also avoid "gRPC port" for the shared endpoint. +- **Legacy gRPC listener**: the separate native gRPC port (`server.grpc.addr`), with its own TLS settings. It is on by default, and `server.legacy_grpc_listener: false` turns it off. It is deprecated. _Avoid_: "the gRPC endpoint", because the shared endpoint also serves gRPC. ## Current decisions ### OPS-002: Metrics and profiler ports stay separate -The metrics and profiler endpoints keep their own ports. They are not on the shared endpoint. +The process metrics endpoint (`/metrics`) and the opt-in profiler of the control plane (`enable_profiler`) keep their own ports. They are not on the shared endpoint. The authenticated metrics route of each organization (`/prom/{org_name}/metrics`) is on the control plane shared endpoint. - Why: they are internal and must not be on the public hostname. - Deciders: migmartri, matiasinsaurralde -- Source: [issue-3628-single-endpoint D-006](../specs/issue-3628-single-endpoint.md) +- Source: [issue-3628-single-endpoint D-006](../specs/issue-3628-single-endpoint.md). For the control plane: the Constraints of [issue-3628-controlplane-http-dispatch](../specs/issue-3628-controlplane-http-dispatch.md). - Code: verified: `app/artifact-cas/internal/server/httpmetrics.go` (separate metrics server), `app/controlplane/internal/server/profiler.go` (pprof on its own port). ### OPS-003: TLS covers all protocols on the shared endpoint -When the operator configures TLS, all protocols on the shared endpoint use it, and an HTTP ingress must use an HTTPS backend. The legacy gRPC listener keeps its own TLS settings during the deprecation period. +When the operator configures TLS, all protocols on the shared endpoint use it. The ingress must use the encrypted HTTP/2 backend setting of its controller (nginx `GRPCS`, Traefik `https`). A shared endpoint with only a certificate or only a key fails at startup. The legacy gRPC listener keeps its own TLS settings, and a partial configuration keeps it plaintext. The chart sets both from one secret. - Why: one certificate and one setting for each service. - Rejected: plain text and TLS on the same port (a second classification step, and a weaker TLS setting). - Deciders: migmartri, matiasinsaurralde -- Source: [issue-3628-single-endpoint D-008](../specs/issue-3628-single-endpoint.md) +- Source: [issue-3628-single-endpoint D-008](../specs/issue-3628-single-endpoint.md). For the control plane: R-001 and R-003 of [issue-3628-controlplane-http-dispatch](../specs/issue-3628-controlplane-http-dispatch.md). - Code: verified: `app/artifact-cas/internal/server/http.go`. The legacy listener keeps `server.grpc.tls_config` (`deployment/chainloop/templates/cas/configmap.yaml`). ### OPS-004: The current HTTP port is the shared endpoint The HTTP port of each service becomes the shared endpoint. The gRPC port is the legacy listener. -- Why: the human-facing hostname, with the status, download, and login endpoints, is already on the HTTP port. Clients that use ingress hostnames do not break in either direction. +- Why: the human-facing hostname, with the status, download, and login endpoints, is already on the HTTP port. Clients that use ingress hostnames do not break. The legacy port serves during the deprecation period. After that, only the backend port of the gRPC ingress changes. Direct clients, for example the control plane connection to the CAS, must move to the shared endpoint before the legacy listener stops. - Rejected: the gRPC port (TLS settings stay in place, but the human-facing hostname stays on a second port). A new port (every client changes). - Deciders: migmartri, matiasinsaurralde -- Source: [issue-3628-single-endpoint D-012](../specs/issue-3628-single-endpoint.md) +- Source: [issue-3628-single-endpoint D-012](../specs/issue-3628-single-endpoint.md). For the control plane: R-001 of [issue-3628-controlplane-http-dispatch](../specs/issue-3628-controlplane-http-dispatch.md). - Code: verified: `app/artifact-cas/internal/server/http.go`, `app/artifact-cas/internal/server/grpc.go` (legacy listener on by default). -### OPS-005: The server process does the multiplexing, with no proxy or sidecar -Each service serves all protocols on its shared endpoint in its own process. It runs on a VM, in Docker Compose, and in Kubernetes with no proxy or sidecar. +### OPS-005: The server process does the multiplexing, with no external proxy or sidecar +Each service serves all protocols on its shared endpoint in its own process. It runs on a VM, in Docker Compose, and in Kubernetes with no separate proxy process or sidecar. The CAS uses an in-process reverse proxy to a private loopback gRPC server ([issue-3628-single-endpoint D-020](../specs/issue-3628-single-endpoint.md)). - Why: no extra process, configuration, or lifecycle in any deployment. -- Rejected: a chart-level proxy or Gateway API routes (extra infrastructure, and two ports stay). A mandatory sidecar or embedded proxy (a process and its lifecycle in every deployment). +- Rejected: a chart-level proxy or Gateway API routes (extra infrastructure, and two ports stay). A mandatory sidecar or a separate proxy process (a process and its lifecycle in every deployment). - Deciders: migmartri, jiparis -- Source: [issue-3628-single-endpoint D-015](../specs/issue-3628-single-endpoint.md) +- Source: [issue-3628-single-endpoint D-015](../specs/issue-3628-single-endpoint.md). For the control plane: [issue-3628-controlplane-http-dispatch D-001](../specs/issue-3628-controlplane-http-dispatch.md). - Code: verified: `app/controlplane/internal/server/http.go`, `app/artifact-cas/internal/server/http.go`. -- Partly replaced: the spec decision also kept native gRPC and plain HTTP on separate upstream connections. [issue-3628-controlplane-http-dispatch D-001](../specs/issue-3628-controlplane-http-dispatch.md) and [issue-3628-single-endpoint D-020](../specs/issue-3628-single-endpoint.md) replaced that part. +- Partly replaced: the spec decision also kept native gRPC and plain HTTP on separate upstream connections. [issue-3628-controlplane-http-dispatch D-001](../specs/issue-3628-controlplane-http-dispatch.md) replaced that part for the control plane. For the CAS, R-010 and D-020 of [issue-3628-single-endpoint](../specs/issue-3628-single-endpoint.md) replaced it. Not shown: 15 smaller or replaced decisions of [issue-3628-single-endpoint](../specs/issue-3628-single-endpoint.md), 2 of [issue-3628-controlplane-http-dispatch](../specs/issue-3628-controlplane-http-dispatch.md). @@ -71,7 +71,7 @@ Not shown: 15 smaller or replaced decisions of [issue-3628-single-endpoint](../s - Why: a paused-reader probe showed that the HTTP adapter can read ahead faster than the application receives. - Deciders: migmartri, jiparis - Source: [issue-3628-controlplane-http-dispatch D-003](../specs/issue-3628-controlplane-http-dispatch.md) -- Code: not confirmed: the CAS has a shared endpoint (`app/artifact-cas/internal/server/http.go`, `app/artifact-cas/internal/server/grpc.go`). See Open conflicts. The parent ticket is open. +- Code: not confirmed. ByteStream still uses the native gRPC transport, through a private loopback gRPC server (`app/artifact-cas/internal/server/grpc.go`). But the CAS also has a shared endpoint (`app/artifact-cas/internal/server/http.go`). See Open conflicts. The parent ticket is open. ## Superseded @@ -79,4 +79,4 @@ None. ## Open conflicts -- OPS-001 ([issue-3628-controlplane-http-dispatch D-003](../specs/issue-3628-controlplane-http-dispatch.md)) and [issue-3628-single-endpoint D-020](../specs/issue-3628-single-endpoint.md) disagree about the CAS transport, and neither has a "replaces" note. The code follows D-020. Proposed: write a spec change that records D-020 as the replacement of dispatch D-003. +- OPS-001 ([issue-3628-controlplane-http-dispatch D-003](../specs/issue-3628-controlplane-http-dispatch.md)) and [issue-3628-single-endpoint D-020](../specs/issue-3628-single-endpoint.md) disagree about whether the CAS gets a shared endpoint. Both keep the native gRPC transport. Neither has a "replaces" note. The code follows D-020. Proposed: write a spec change that records D-020 as the replacement of dispatch D-003. From 8b095e4e41a97af6ce3399fa4a04133cf50aab21 Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino <miguel@chainloop.dev> Date: Tue, 3 Nov 2026 22:31:02 +0100 Subject: [PATCH 4/6] docs(decisions): record CAS-009, a fixed existence cache lifetime The existence cache lifetime stays at 24 hours with no setting and no off switch. This replaces issue-3543 D-007 and the settings of R-004. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: e3100522-144f-4450-91f7-eed1cd2c29d9 --- docs/decisions/artifact-cas.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/decisions/artifact-cas.md b/docs/decisions/artifact-cas.md index 63304d16d..153b36aa4 100644 --- a/docs/decisions/artifact-cas.md +++ b/docs/decisions/artifact-cas.md @@ -94,6 +94,14 @@ Before it answers 304, the CAS makes a metadata call to the backend. A deleted o - Source: [issue-3549-cas-download-browser-cache D-005](../specs/issue-3549-cas-download-browser-cache.md) - Code: verified: `app/artifact-cas/internal/service/download.go`. +### CAS-009: The existence cache lifetime is fixed, with no off switch +Replaces [issue-3543-cas-existence-cache D-007](../specs/issue-3543-cas-existence-cache.md) and the settings of R-004. An existence entry expires after 24 hours. No setting changes the lifetime or turns the cache off. +- Why: blobs cannot be deleted through Chainloop, so a fixed 24-hour bound covers the only risk. A setting adds configuration that no operator needs. +- Rejected: a configurable lifetime and an operator switch that turns the cache off (spec D-007 and R-004). +- Deciders: migmartri +- Source: recorded directly, 2026-11-03 +- Code: verified: `pkg/cache/casexistence/casexistence.go` (`ttl` constant), `app/artifact-cas/cmd/wire.go` (the cache is always on). + Not shown: 5 smaller decisions of [issue-3543-cas-existence-cache](../specs/issue-3543-cas-existence-cache.md), 2 of [issue-3549-cas-download-browser-cache](../specs/issue-3549-cas-download-browser-cache.md). ## Non-Confirmed From a69a51a0a0037817ad5208649623a24dbb687b1f Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino <miguel@chainloop.dev> Date: Tue, 3 Nov 2026 22:34:35 +0100 Subject: [PATCH 5/6] docs(decisions): address the open review comments and fix the Not shown counts Scope CAS-005 to the HTTP download endpoint, AUT-006 to requests that pass field validation, AUT-007 to a missing JSON/REST mapping, and the Trace to CAS relationship to pushes. Count dispatch D-001 as shown, because OPS-005 now cites it. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: e3100522-144f-4450-91f7-eed1cd2c29d9 --- docs/decisions/README.md | 2 +- docs/decisions/artifact-cas.md | 4 ++-- docs/decisions/authentication.md | 4 ++-- docs/decisions/service-operations.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 73e81cbbb..5dd0abc37 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -74,7 +74,7 @@ Last refresh: 2026-11-01, commit 4e150ca7. ## Relationships - **Attestation Process → Chainloop Trace:** both read `.chainloop.yml` through one reader (ATT-003, attestation-process.md). A change to the file order or keys changes both areas. -- **Chainloop Trace → Artifact CAS:** Trace uploads each spec source, skill package, and pasted image as its own material. A transcript pointer replaces a copy only when its material is stored (TRC-016, chainloop-trace.md). +- **Chainloop Trace → Artifact CAS:** On a push, Trace uploads each spec source, skill package, and pasted image as a material. Local mode uses neither. A transcript pointer replaces a copy only when its material is stored (TRC-016, chainloop-trace.md). - **Service Operations → Artifact CAS, Authentication:** the shared endpoints carry the CAS ByteStream and download routes and the control plane login endpoints. A change to their dispatch or TLS affects all of them. It does not affect ByteStream traffic on the legacy gRPC listener, which the chart uses by default for the control plane. ## Repository-wide decisions diff --git a/docs/decisions/artifact-cas.md b/docs/decisions/artifact-cas.md index 153b36aa4..20414f63f 100644 --- a/docs/decisions/artifact-cas.md +++ b/docs/decisions/artifact-cas.md @@ -63,7 +63,7 @@ The client cache, which holds backend credentials, stays in process memory. It n - Code: verified: `app/artifact-cas/internal/service/cache.go` (in-process expirable LRU). ### CAS-005: Downloads use the browser HTTP cache -The CAS sets cache headers on downloads and answers 304 when the browser has the content. The control plane keeps no copy of the bytes. +The HTTP download endpoint of the CAS sets cache headers and answers 304 when the browser has the content. ByteStream downloads do not change. The control plane keeps no copy of the bytes. - Why: the browser downloads straight from the CAS, so a cache there skips the backend copy and the transfer. - Rejected: a server cache in the control plane (the control plane never sees the bytes, and its message size limit is too small). - Deciders: jiparis, migmartri @@ -102,7 +102,7 @@ Replaces [issue-3543-cas-existence-cache D-007](../specs/issue-3543-cas-existenc - Source: recorded directly, 2026-11-03 - Code: verified: `pkg/cache/casexistence/casexistence.go` (`ttl` constant), `app/artifact-cas/cmd/wire.go` (the cache is always on). -Not shown: 5 smaller decisions of [issue-3543-cas-existence-cache](../specs/issue-3543-cas-existence-cache.md), 2 of [issue-3549-cas-download-browser-cache](../specs/issue-3549-cas-download-browser-cache.md). +Not shown: 5 smaller or replaced decisions of [issue-3543-cas-existence-cache](../specs/issue-3543-cas-existence-cache.md), 2 of [issue-3549-cas-download-browser-cache](../specs/issue-3549-cas-download-browser-cache.md). ## Non-Confirmed diff --git a/docs/decisions/authentication.md b/docs/decisions/authentication.md index 3f474d9fa..1d4f3e731 100644 --- a/docs/decisions/authentication.md +++ b/docs/decisions/authentication.md @@ -72,7 +72,7 @@ When the CLI sent a challenge and gets `?t=`, it accepts the token with a warnin - Code: verified: `app/cli/cmd/auth_login.go`. ### AUT-006: One error to the client for each exchange failure -Each exchange failure gives the client the same error. The specific reason goes to the server log only. +Each exchange failure gives the client the same error. The specific reason goes to the server log only. A request that fails field validation (an empty or too long field) gets the validation error before the exchange runs. - Why: a uniform client error gives no hint to a person who guesses codes. The server log reason helps operators debug failed logins. - Rejected: a uniform error with no server log reason (hard to debug). - Deciders: migmartri, jiparis @@ -80,7 +80,7 @@ Each exchange failure gives the client the same error. The specific reason goes - Code: verified: `app/controlplane/internal/service/auth.go`. ### AUT-007: The exchange endpoint is gRPC only -The exchange is an unauthenticated RPC on the auth service, with no HTTP mapping. +The exchange is an unauthenticated RPC on the auth service, with no JSON/REST mapping. Clients call it over gRPC or gRPC-Web. - Why: the CLI uses gRPC and the web UI uses gRPC-Web, so no client needs a plain HTTP route. With no HTTP route, the public surface of the unauthenticated endpoint is smaller, and the web UI makes no cross-origin preflight. - Rejected: keep an HTTP mapping for a future non-gRPC client (no such client exists). - Deciders: migmartri, matiasinsaurralde diff --git a/docs/decisions/service-operations.md b/docs/decisions/service-operations.md index 26990270d..7b60d9bd8 100644 --- a/docs/decisions/service-operations.md +++ b/docs/decisions/service-operations.md @@ -63,7 +63,7 @@ Each service serves all protocols on its shared endpoint in its own process. It - Code: verified: `app/controlplane/internal/server/http.go`, `app/artifact-cas/internal/server/http.go`. - Partly replaced: the spec decision also kept native gRPC and plain HTTP on separate upstream connections. [issue-3628-controlplane-http-dispatch D-001](../specs/issue-3628-controlplane-http-dispatch.md) replaced that part for the control plane. For the CAS, R-010 and D-020 of [issue-3628-single-endpoint](../specs/issue-3628-single-endpoint.md) replaced it. -Not shown: 15 smaller or replaced decisions of [issue-3628-single-endpoint](../specs/issue-3628-single-endpoint.md), 2 of [issue-3628-controlplane-http-dispatch](../specs/issue-3628-controlplane-http-dispatch.md). +Not shown: 15 smaller or replaced decisions of [issue-3628-single-endpoint](../specs/issue-3628-single-endpoint.md), 1 of [issue-3628-controlplane-http-dispatch](../specs/issue-3628-controlplane-http-dispatch.md). ## Non-Confirmed From a2eae5afd02d6ae0b483739ac6a39053d186872d Mon Sep 17 00:00:00 2001 From: Miguel Martinez Trivino <miguel@chainloop.dev> Date: Tue, 3 Nov 2026 22:45:31 +0100 Subject: [PATCH 6/6] docs(decisions): address the second review round Scope OPS-003 to the shared endpoint ingress, restate the CAS legacy listener in the relationships, cite R-003 for ATT-002, and say that the TRC-001 fallback is not built. Name replaced decisions in the Trace Not shown line. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: e3100522-144f-4450-91f7-eed1cd2c29d9 --- docs/decisions/README.md | 2 +- docs/decisions/attestation-process.md | 2 +- docs/decisions/chainloop-trace.md | 4 ++-- docs/decisions/service-operations.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 5dd0abc37..db47cb07c 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -75,7 +75,7 @@ Last refresh: 2026-11-01, commit 4e150ca7. - **Attestation Process → Chainloop Trace:** both read `.chainloop.yml` through one reader (ATT-003, attestation-process.md). A change to the file order or keys changes both areas. - **Chainloop Trace → Artifact CAS:** On a push, Trace uploads each spec source, skill package, and pasted image as a material. Local mode uses neither. A transcript pointer replaces a copy only when its material is stored (TRC-016, chainloop-trace.md). -- **Service Operations → Artifact CAS, Authentication:** the shared endpoints carry the CAS ByteStream and download routes and the control plane login endpoints. A change to their dispatch or TLS affects all of them. It does not affect ByteStream traffic on the legacy gRPC listener, which the chart uses by default for the control plane. +- **Service Operations → Artifact CAS, Authentication:** the shared endpoints carry the CAS ByteStream and download routes and the control plane login endpoints. A change to their dispatch affects all of them. It does not affect ByteStream traffic on the legacy gRPC listener of the CAS. That listener is on by default, and the chart sends the control plane connection to the CAS to it. The chart sets the TLS of both listeners from one secret. ## Repository-wide decisions diff --git a/docs/decisions/attestation-process.md b/docs/decisions/attestation-process.md index fd8a25a6c..17d0ce01a 100644 --- a/docs/decisions/attestation-process.md +++ b/docs/decisions/attestation-process.md @@ -44,7 +44,7 @@ The attestation commands that contact the control plane read the organization fr - Why: with remote state, a command must connect to the correct organization before it can load the state. - Deciders: migmartri, javirln - Source: [issue-3063-attestation-repo-config D-004](../specs/issue-3063-attestation-repo-config.md) -- Code: verified: `app/cli/cmd/attestation.go`. `attestation verify` does not read the file, and the spec does not ask it to. +- Code: verified: the five commands that R-003 lists read the file (`app/cli/cmd/attestation.go`). `attestation verify` does not read it. It calls only the trusted root service, which needs no organization (`app/cli/pkg/action/attestation_verify.go`). ### ATT-003: One reader of `.chainloop.yml` for attestation and trace Attestation commands and Trace read the repository config through one package. diff --git a/docs/decisions/chainloop-trace.md b/docs/decisions/chainloop-trace.md index 9656e7917..588e64e89 100644 --- a/docs/decisions/chainloop-trace.md +++ b/docs/decisions/chainloop-trace.md @@ -38,7 +38,7 @@ Structure. The index (README.md) has the parts Areas (the area map), Relationshi ### TRC-001: The agent decides what the spec is An instruction at session start tells the agent to resolve the spec of the session and write it into the spec folder. - Why: only the agent knows which input sets the task, and it reaches sources that the transcript never holds. -- Rejected: extraction from the transcript by the CLI. It needs rules to pick the spec, misses sources that the agent never opened, and needs one extractor for each agent. Extraction on the server after ingestion (the spec is not in the attestation). Both stay as a fallback if the capture rate is poor. +- Rejected: extraction from the transcript by the CLI. It needs rules to pick the spec, misses sources that the agent never opened, and needs one extractor for each agent. Extraction on the server after ingestion (the spec is not in the attestation). The spec keeps both as a possible fallback if the capture rate is poor. Neither is built. - Deciders: jiparis, migmartri - Source: [issue-3495-session-spec-capture D-001](../specs/issue-3495-session-spec-capture.md) - Code: verified: `app/cli/pkg/action/trace_spec.go`, `app/cli/pkg/action/trace_agent_hook.go`. @@ -327,7 +327,7 @@ The provider keeps a private copy of the native rollout. The evidence parser rea - Source: [issue-3657-codex-trace-provider D-003](../specs/issue-3657-codex-trace-provider.md) - Code: verified: `app/cli/internal/trace/codex/copy.go`, `app/cli/internal/trace/codex/parse.go`, `app/cli/internal/trace/codex/pricing.go`. -Not shown: the smaller decisions of each spec. +Not shown: the smaller or replaced decisions of each spec. - 10 of [issue-3495-session-spec-capture](../specs/issue-3495-session-spec-capture.md) - 8 of [issue-3515-spec-capture-during-session](../specs/issue-3515-spec-capture-during-session.md) - 12 of [issue-3520-pi-trace-provider](../specs/issue-3520-pi-trace-provider.md) diff --git a/docs/decisions/service-operations.md b/docs/decisions/service-operations.md index 7b60d9bd8..4c4f2ec45 100644 --- a/docs/decisions/service-operations.md +++ b/docs/decisions/service-operations.md @@ -39,7 +39,7 @@ The process metrics endpoint (`/metrics`) and the opt-in profiler of the control - Code: verified: `app/artifact-cas/internal/server/httpmetrics.go` (separate metrics server), `app/controlplane/internal/server/profiler.go` (pprof on its own port). ### OPS-003: TLS covers all protocols on the shared endpoint -When the operator configures TLS, all protocols on the shared endpoint use it. The ingress must use the encrypted HTTP/2 backend setting of its controller (nginx `GRPCS`, Traefik `https`). A shared endpoint with only a certificate or only a key fails at startup. The legacy gRPC listener keeps its own TLS settings, and a partial configuration keeps it plaintext. The chart sets both from one secret. +When the operator configures TLS, all protocols on the shared endpoint use it. The ingress of the shared endpoint must use the encrypted HTTP/2 backend setting of its controller (nginx `GRPCS`, Traefik `https`). A shared endpoint with only a certificate or only a key fails at startup. The legacy gRPC listener keeps its own TLS settings, and a partial configuration keeps it plaintext. The chart sets both from one secret. - Why: one certificate and one setting for each service. - Rejected: plain text and TLS on the same port (a second classification step, and a weaker TLS setting). - Deciders: migmartri, matiasinsaurralde