Commit graph

20 commits

Author SHA256 Message Date
CTO (Paperclip)
47a7373ef2 transparency-log: add GET /api/v1/log/entries/{hash} + /healthz (DEV-613)
The verification-service and testagent-transform clients both call
`GET /api/v1/log/entries/{content_hash}` to read a stored envelope and
inclusion proof back out of the log, but the server previously only
implemented the POST (write) half of ADR-0005. Add:

- `Backend.Get` plus `backend.Entry` and `ErrEntryNotFound` so the
  memory backend can serve the read; Trillian stays stubbed and
  continues returning 503 (`ErrTrillianUnavailable`) until a later
  subtask wires the real read path.
- `GET /api/v1/log/entries/{hash}` on the HTTP server, routing 404 /
  503 / 500 with the same error envelope as the POST half.
- `GET /healthz` so the Phase-1 smoke-test harness can wait for the
  service to come up instead of racing against its bind.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 20:57:52 +00:00
CTO (Paperclip)
0ae29ae342 verification-service: Phase-1 two-stage chain verifier (approval -> transform) (DEV-612)
Some checks failed
validate / Rego (opa fmt + opa test) (push) Successful in 16s
validate / JSON Schema (ajv) (push) Successful in 20s
build-images / signing-service image (push) Successful in 24s
validate / DB (migrate up/down dry-run) (push) Failing after 26s
validate / Go (vet + test) (push) Successful in 1m16s
Adds services/verification-service exposing POST /api/v1/verify/chain
for sink:dummy-sink. The handler runs the six hard-coded chain checks
described in DEV-612 and reserves decision_id in a new
`verification_decisions` table so a replayed sink delivery is refused
without recomputing signatures.

- DSSE signature verification against configurable approval + transform
  signer key_ids, loaded from VERIFICATION_SIGNER_KEYS (pem/hex/b64/b64u)
- RFC 6962 inclusion-proof verifier (shared leaf/node hashing with
  services/transparency-log/pkg/backend)
- Content binding: subject.digest.sha256 must match predicate.content_hash
  (approval) and request.payload_hash (transform)
- Hard-coded coverage of allowed_transformations / allowed_sinks /
  expires_at predicates (OPA deferred to Phase 2 per the subtask)
- Replay guard via new db/trust/migrations/0005_verification_decisions
  (in-memory store ships; Postgres skeleton mirrors sibling pattern)
- Dockerfile mirrors signing-service pattern (distroless static + nonroot)
- README documents endpoint, reason-code taxonomy, required predicate
  fields, and the Phase-2 non-scope

Tests: happy path + 7 negatives (bad signature, missing inclusion proof,
hash drift, sink not allowed, expired, replay, operation not allowed).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 20:35:35 +00:00
CTO (Paperclip)
b45ad34169 dummy-sink: Phase-1 mock sink (sink:dummy-sink) (DEV-611)
All checks were successful
validate / JSON Schema (ajv) (push) Successful in 19s
build-images / signing-service image (push) Successful in 22s
validate / Rego (opa fmt + opa test) (push) Successful in 13s
validate / DB (migrate up/down dry-run) (push) Successful in 26s
validate / Go (vet + test) (push) Successful in 1m13s
Trivial Senken-Adapter per §7 of architecture V1.1. Accepts
{decision_id, payload_hash, payload, approval_ref, transform_ref}
on POST /api/v1/sink/dummy/deliver; calls verification-service's
/api/v1/verify/chain and branches:

- valid=true → atomic .tmp+rename write of payload to
  /data/dummy-sink/<decision_id>.json, builds a
  basicstack.de/attestations/handover/v1 statement, signs via
  signing-service with the double identity flow (Pocket-ID bearer
  + projected SA token), submits DSSE envelope to the TLog and
  returns 201 {handover_ref, log_index}.
- valid=false & replay=true → 409.
- valid=false → 403 {reasons:[…]}.

Replay protection lives in verification-service per the parent plan;
dummy-sink keeps no local replay store. The payload_hash field is
verified against both the raw and canonical form so a verifier can
reproduce either hash.

Tests use httptest-backed fakes for verify / sign / tlog and cover
the DEV-611 acceptance criteria plus the bad-request shapes:
- happy path writes the file, calls signer with the handover
  statement, submits the envelope to the TLog
- 403 keeps the disk and never calls signer/tlog
- replay returns 409 without re-signing
- payload_hash mismatch rejects before verify
- malformed bodies return 400
- verify unavailable → 502, signing denied → 403

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 20:20:40 +00:00
CTO (Paperclip)
cfb5e29b54 admin-api: go mod tidy after signing-service go 1.26 bump
Some checks failed
build-images / signing-service image (push) Failing after 15s
validate / JSON Schema (ajv) (push) Successful in 19s
validate / Rego (opa fmt + opa test) (push) Successful in 13s
validate / DB (migrate up/down dry-run) (push) Failing after 25s
validate / Go (vet + test) (push) Successful in 1m13s
[DEV-609](/DEV/issues/DEV-609) bumped services/signing-service to
go 1.26.0 (required by github.com/open-policy-agent/opa v1.21.4).
admin-api replaces basicstack.de/proofedmesh/services/signing-service
with the sibling via a replace directive, so its go.mod effective
minimum is also 1.26 and the transitive indirect requirements (opa,
pgx, jwx, go-jose, levenshtein, …) must be listed. CI surfaced
this as:

    go: updates to go.mod needed; to update it:
        go mod tidy

Fix: `go mod tidy` on services/admin-api with the 1.26 toolchain.
Local `go vet` + `go test ./...` pass on all admin-api packages.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 20:00:58 +00:00
CTO (Paperclip)
47f295d864 ci: bump Go image to 1.26-alpine for signing-service dep
Some checks failed
validate / JSON Schema (ajv) (push) Successful in 19s
build-images / signing-service image (push) Failing after 24s
validate / Go (vet + test) (push) Failing after 5s
validate / Rego (opa fmt + opa test) (push) Successful in 13s
validate / DB (migrate up/down dry-run) (push) Failing after 20s
signing-service depends on github.com/open-policy-agent/opa v1.21.4,
which requires Go >= 1.26.0. The CI Go image and the signing-service
Dockerfile were still pinned to golang:1.23-alpine3.21, so the
"Go (vet + test)" job and the signing-service image build both
failed on main as soon as [DEV-609](/DEV/issues/DEV-609) landed:

    go: module ../signing-service requires go >= 1.26.0
         (running go 1.23.12; GOTOOLCHAIN=local)

Fix: pin both to golang:1.26-alpine3.23 (oldest alpine tag that
exists for the 1.26 line). All other modules declare go 1.23 which
1.26 still accepts, so no downstream churn.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 19:59:05 +00:00
CTO (Paperclip)
ba1b88d490 testagent-transform: Phase-1 trust-chain demo agent (DEV-610)
Some checks failed
build-images / signing-service image (push) Failing after 16s
validate / JSON Schema (ajv) (push) Successful in 19s
validate / Go (vet + test) (push) Failing after 4s
validate / Rego (opa fmt + opa test) (push) Successful in 13s
validate / DB (migrate up/down dry-run) (push) Failing after 21s
Minimal CLI agent process that end-to-end demonstrates the Transform step
of the ProofedMesh trust chain:

  1. Fetches an approval attestation from the transparency-log by
     attestation_ref (`sha256:<hex>`).
  2. DSSE-verifies the fetched envelope against a pinned approval-signer
     ed25519 keyring; stops before any signing call on failure.
  3. Builds an in-toto v1 Statement with predicateType
     `basicstack.de/attestations/transform/v1`, operation
     `expense.map_to_sink`, materials[0].attestation_ref = approval_ref,
     and subject[0].digest.sha256 = sha256(canonical(handover_payload)).
  4. Calls signing-service /api/v1/sign/dsse with BOTH identities:
     Authorization: Bearer <pocket-id client_credentials token> AND
     X-Workload-Token: <projected SA token from
     /var/run/secrets/tokens/trust-api>.
  5. Submits the returned DSSE envelope + content_hash to the
     transparency-log /api/v1/log/entries.
  6. POSTs the canonical handover_payload bytes to the dummy-sink
     (schema sink.dummy.v1), carrying the transform content_hash on the
     X-Attestation-Ref header.

Tests use httptest for all four backends:

  - Happy path (verifies the bytes sent to the sink match the signed
    subject digest and that the TLog recorded the same content_hash the
    agent returned).
  - Invalid approval signature -> stops before sign-call; TLog and sink
    untouched.
  - Signing-service returns 403 signing_denied -> surfaced as
    signer.ErrSigningDenied; TLog and sink untouched.
  - Missing Pocket-ID token -> fails before any network call.

Includes PocketID client_credentials TokenSource (OIDC discovery + cache)
and ProjectedSATokenSource (reads kubelet-managed Volume) mirroring
admin-api/approval-service.

Non-scope: no read-side attestations (Phase 2), no K8s/CI image build
wiring (follow-up ticket).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 19:55:02 +00:00
CTO (Paperclip)
30dd2f8b65 signing-service: Pocket-ID JWKS + K8s TokenReview + OPA (DEV-609)
Wires the signing-service's production path for ProofedMesh Phase 1:
the service now performs a double identity check (Pocket-ID OIDC JWT
+ K8s ServiceAccount TokenReview), reconciles both against the
trust-api `agents` registry, and asks an OPA/Rego policy whether the
caller may sign with the requested key_ref.

Code
- internal/token/pocket_id.go: OIDC-Discovery-backed JWKS validator
  with automatic kid rotation (coreos/go-oidc).
- internal/token/k8s_tokenreview.go: hand-rolled TokenReview client;
  reads X-Workload-Token, posts to authentication.k8s.io/v1 via the
  pod's in-cluster SA token + CA.
- internal/registry: Postgres resolver for the agents table +
  in-memory Static resolver for tests.
- internal/policy/opa.go: OPA/rego StructuredDecider; input carries
  both lookups so the policy can enforce identity consistency AND
  the key_ref ↔ role mapping.
- internal/httpapi/sign.go: new structured flow when
  Validator+TokenReviewer+Registry+StructuredPolicy are all wired;
  dev path (static bearers + AllowAll) remains intact.
- cmd/signing-service/main.go: -pocket-id-issuer,
  -pocket-id-audience, -k8s-tokenreview, -registry-dsn,
  -policy-bundle flags.

Policy
- policies/sinks/signing_service.rego: identities_agree +
  key_ref_matches_role rules; agent → key_ref prefix map covers
  approval-service, testagent-transform, dummy-sink.

Deploy
- deploy/signing-service/01-rbac.yaml: ClusterRole with
  tokenreviews.create + binding to the signing-service SA.
- deploy/signing-service/20-deployment.yaml: adds POCKET_ID_ISSUER,
  POCKET_ID_AUDIENCE, TRUST_PG_DSN env, projected SA token volume,
  OPA policy ConfigMap mount, structured flags.
- deploy/signing-service/00-serviceaccount.yaml: disables default
  auto-mount; a projected volume is used.
- deploy/signing-service/README.md: documents prod flags +
  ConfigMap generation.

Tests
- httptest-mock Pocket-ID (OIDC discovery + JWKS, kid rotation),
  StaticTokenReviewer fake, Static registry, real Rego policy.
- Happy paths: Approval-Service → trust-api/approval-signer-2026-q4;
  testagent-transform → trust-api/agent-testagent-transform-2026-q4.
- Denies: registry mismatch, wrong key_ref for role, missing /
  bad workload token, bad Pocket-ID token, kid rotation.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 19:42:40 +00:00
Paperclip
3a01cc651f approval-service: Phase-1 content-binding approval service (DEV-608)
New services/approval-service with the Session → Preview → Confirm
pipeline required by DEV-606/DEV-608:

* POST /api/v1/sessions starts a Pocket-ID-bound approval session.
* POST /api/v1/approvals/preview renders content server-side, computes
  H_content = sha256(canonical JSON), stores the (nonce, salt, canonical,
  challenge) tuple and returns the human verification digits plus the
  WebAuthn challenge = nonce || H_content.
* POST /api/v1/approvals/confirm verifies the WebAuthn assertion against
  the stored challenge, DSSE-signs the Approval statement via the
  signing-service (key_ref=trust-api/approval-signer-2026-q4), submits
  to the transparency-log, and returns {approval_id, attestation_ref,
  log_index}. TLog failures leave the row at attestation-pending for
  the reconciler.

* db/trust/migrations/0004_approval_sessions.{up,down}.sql: new tables
  approval_sessions, approval_previews, approvals (schema matches the
  Phase 1 store contract).

* internal/content: canonical JSON + H_content + 6-digit verification
  code (byte-compatible with signing-service/internal/canonical).
* internal/webauthn: stdlib-only Phase-1 verifier; wire shape stays
  WebAuthn-compatible for the Phase-2 swap to go-webauthn/webauthn.
* internal/signer, internal/tlog: HTTP clients mapping 5xx / connection
  errors to typed ErrSignerUnavailable / ErrTLogUnavailable so the
  HTTP layer can return 502 for signing failures and persist
  attestation-pending on TLog failure.
* internal/service.Orchestrator ties the pipeline together and persists
  the Approval BEFORE the TLog submit (never return a log_index for
  an un-persisted approval).

Tests cover: happy path, tampered H_content → 400, expired challenge
→ 403, signer 5xx + conn-refused → 502, TLog failure → row stays
attestation-pending, duplicate confirm → 409, session expired → 403.
go vet clean, go test ./... passes.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 19:42:06 +00:00
CTO
951f22f80e transparency-log: real Trillian backend wiring + agent-trust deploy (DEV-604)
Some checks failed
build-images / signing-service image (push) Failing after 14s
validate / JSON Schema (ajv) (push) Successful in 21s
validate / Rego (opa fmt + opa test) (push) Successful in 14s
validate / Go (vet + test) (push) Successful in 30s
- services/transparency-log/pkg/backend/trillian.go: real gRPC client
  on github.com/google/trillian v1.6.1. Submit() does QueueLeaf with the
  canonical DSSE envelope as LeafValue and the raw content_hash bytes as
  LeafIdentityHash, then polls GetLatestSignedLogRoot +
  GetInclusionProofByHash until the leaf is sequenced. Idempotency uses
  Trillian's ALREADY_EXISTS signal on the inner QueuedLogLeaf.Status, so
  a repeated submit still returns 200 OK with the originally-assigned
  log_index + a valid proof against the current STH.
- Unconfigured backend (missing endpoint or LogID) returns the historical
  ErrTrillianUnavailable so the httpapi layer keeps mapping it to 503
  instead of silently 500. Existing stub-returns-503 test updated.
- cmd/transparency-log: pointer-receiver Trillian wiring; flag docs
  mention the production SRV name.
- services/transparency-log/Dockerfile: distroless multi-stage build
  used by deploy/trillian/40-transparency-log.yaml.

- deploy/trillian/: full K8s stack for the agent-trust namespace —
  MySQL 8.4 StatefulSet (schema init ConfigMap vendored from upstream
  storage.sql v1.6.1), Trillian LogServer + LogSigner Deployments on
  gcr.io/trillian-opensource-ci:v1.6.1, createtree Job that writes
  the TreeID to a ConfigMap (re-apply safe), transparency-log
  Deployment with --backend=trillian pulling TRILLIAN_LOG_ID from
  the ConfigMap.
- deploy/trillian/README.md: first-time apply + smoke-test + secret
  rotation + signer-key clarification (STH key is Trillian-internal;
  DSSE envelope signing stays with signing-service / OpenBao).

- services/admin-api: go.mod / go.sum pick up the new transitive deps
  (trillian + grpc) via the local replace on transparency-log.
- services/transparency-log/README.md: Backends table updated; next-
  steps list no longer carries Trillian-deployment as a follow-up.

Deployed to the live cluster from this heartbeat: MySQL + LogServer +
LogSigner running in agent-trust, tree id 8482455225499059043, service
smoke-tested end-to-end (201 on first POST, 200 on idempotent repeat,
409 on content_hash mismatch, tree_size=2 with correct audit path).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 18:39:44 +00:00
CTO
b52936ce9d signing-service: Dockerfile, K8s manifests (agent-trust), image CI, secret convention
Some checks failed
validate / JSON Schema (ajv) (push) Successful in 20s
validate / Rego (opa fmt + opa test) (push) Successful in 14s
validate / Go (vet + test) (push) Successful in 21s
build-images / signing-service image (push) Failing after 13s
- Dockerfile: multi-stage (golang:1.23-alpine3.21 → distroless/static:nonroot),
  CGO off, trimpath, stripped, UID/GID 65532, read-only rootfs friendly.
- deploy/signing-service/: ServiceAccount, Deployment (2 replicas, probes,
  tight SecurityContext), ClusterIP Service on :8081, NetworkPolicy
  (ingress from agent-trust, DNS egress), example Secret, and README with
  the full dev + prod secret convention.
- .forgejo/workflows/build-images.yml: builds image on push + PR,
  pushes :<git-sha> + :main to harbor.basicstack.de on main pushes only.
  Clone pattern matches validate.yml (sha256 repo, manual git clone).
- README updates: service README gains Deployment section; repo README
  layout now mentions services/signing-service, services/transparency-log,
  deploy/, and the new workflow.

Scope matches DEV-605. OpenBao, OPA and Pocket-ID JWKS stay stubs and move
in their own follow-up subtasks; the prod secret shape is documented so
those subtasks can plug in without reshaping the Deployment.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 18:19:51 +00:00
CTO Agent
5d338dd725 signing-service: public pkg/httpapi test surface + admin-api interop test (DEV-599)
All checks were successful
validate / Rego (opa fmt + opa test) (push) Successful in 13s
validate / JSON Schema (ajv) (push) Successful in 19s
validate / Go (vet + test) (push) Successful in 22s
- services/signing-service/pkg/httpapi exposes NewTestHandler so sibling
  modules can build the real signing-service in-process (same pattern
  transparency-log already uses).
- services/admin-api/internal/attest/interop_signing_test.go drives the real
  attest.HTTPSigner against the real signing-service handler and asserts the
  end-to-end ADR-0005 round-trip: envelope shape, keyid, content_hash
  self-consistency across canonicalisers, Ed25519 signature verification
  with the server's public key, and canonical-payload byte equality.

Closes the "Interop-Test: admin-api attest.HTTPSigner-Client erfolgreich
gegen diesen Server" acceptance criterion from DEV-599.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 18:11:40 +00:00
CTO (Paperclip)
9f1eb84032 admin-api: Pocket-ID session-cookie verifier (OIDC Authorization-Code + PKCE, JWKS, group check)
All checks were successful
validate / Go (vet + test) (push) Successful in 23s
validate / Rego (opa fmt + opa test) (push) Successful in 15s
validate / JSON Schema (ajv) (push) Successful in 20s
- internal/auth/pocket_id.go: Middleware mit /login + /callback,
  OIDC-Discovery + JWKS-Cache, RS256-Signatur-Prüfung, PKCE S256,
  AES-256-GCM-versiegeltes Session-Cookie, Erzwingung von
  Gruppenmitgliedschaft 'proofedmesh-admin' (konfigurierbar).
  Protect(h) setzt WithPrincipal(sub) + WithSession(sid) auf ctx;
  fehlendes/ungültiges Cookie → 302 nach /login (next= bewahrt).
- internal/auth/context.go: WithPrincipal/WithSession/PrincipalFrom
  /SessionFrom aus httpapi/rates.go in internal/auth gezogen,
  damit auth und httpapi dieselben Kontext-Keys teilen (keine
  import cycle: httpapi importiert auth).
- httpapi/rates.go: delegiert jetzt an auth.*; die Shim-Exports
  WithPrincipal/WithSession bleiben für bestehende Tests.
- cmd/admin-api/main.go: Pocket-ID-Middleware aktivieren, sobald
  -oidc-client-id, -oidc-client-secret, -oidc-redirect-url und
  -session-cookie-secret gesetzt sind; ohne sie bleibt das
  Stub-Admin-Prinzipal (bestehendes Verhalten für Dev-Mode).
- Tests (10 neue, 36/36 insgesamt grün, go vet clean):
  httptest-Mock-Pocket-ID mit JWKS + /token-Endpoint deckt den
  vollständigen Flow ab, inkl. missing cookie, falsche Gruppe,
  Nonce/State-Mismatch, Audience-Mismatch, abgelaufenes ID-Token,
  unbekannte kid, und Session-Ablauf nach TTL.
- README: Auth-Abschnitt + Konfigurations-Flags aktualisiert.

DEV-603

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 18:10:02 +00:00
CTO
69b3f3012f services: add transparency-log Go service (ADR-0005 POST /api/v1/log/entries)
All checks were successful
validate / Rego (opa fmt + opa test) (push) Successful in 14s
validate / JSON Schema (ajv) (push) Successful in 20s
validate / Go (vet + test) (push) Successful in 20s
- services/transparency-log/pkg/backend: Backend interface with in-memory
  append-log (RFC 6962 Merkle tree) + Trillian wrapper (prod stub).
- Idempotenz-Pflicht: Wiederaufruf mit identischem content_hash liefert
  200 + gleichen log_index + aktuellen inclusion_proof.
- content_hash-Verifikation serverseitig; Mismatch → 409.
- pkg/httpapi: ADR-0005-konformer Handler (201/200/400/404/409/503).
- cmd/transparency-log: minimaler HTTP-Server, memory default,
  --backend trillian als Stub (503).
- Interop-Test in services/admin-api/internal/tlog/interop_test.go
  fährt die echte transparency-log pkg gegen die admin-api
  HTTPSubmitter — Happy-Path, Idempotenz und content_hash-Mismatch
  abgedeckt.
- go test: 10 Tests transparency-log + 2 Interop-Tests admin-api, go
  vet clean in beiden Modulen.

Folge-Subtask: Trillian-Deployment + Keys (siehe ADR-0005 Open Points).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 18:08:53 +00:00
CTO
2e44147a71 services: add attest-reconciler Go service (periodic worker re-signs per_diem_rates rows without rate_change_attestation_ref via Signer+TLog, 11/11 go tests pass, go vet clean)
All checks were successful
validate / JSON Schema (ajv) (push) Successful in 19s
validate / Go (vet + test) (push) Successful in 19s
validate / Rego (opa fmt + opa test) (push) Successful in 14s
- services/attest-reconciler/ as its own go.mod (admin-api internal/ is not importable; shared code gets extracted when a 3rd consumer appears).
- Scheduler accepts "@every <dur>" or a bare Go duration; context-cancellation stops the loop cleanly.
- Store client defines PendingRateStore + Postgres skeleton for SELECT ... WHERE rate_change_attestation_ref IS NULL.
- Orchestrator + HTTPSigner + tlog.HTTPSubmitter mirror admin-api byte-for-byte so the TLog idempotency key collides on resubmits (kein Doppelsignieren).
- Prometheus text-format /metrics endpoint, dependency-free: attest_reconciler_dirty_rows_total, _reconciliation_attempts_total, _reconciliation_failures_total, _idempotent_noop_total.
- Tests with fake store + fake signer + fake submitter cover: happy path, already-attested rows are NOT re-signed, TLog idempotency across ticks, TLog-down failure path, pre-cancelled ctx.
- Top-level README reflects the new service; CI auto-discovers services/*/go.mod so no workflow change needed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 18:06:36 +00:00
CTO (Paperclip)
be3ac9cdb8 admin-api: Pocket-ID client_credentials TokenSource with OIDC discovery + cache (replaces StaticToken in prod)
All checks were successful
validate / JSON Schema (ajv) (push) Successful in 20s
validate / Go (vet + test) (push) Successful in 19s
validate / Rego (opa fmt + opa test) (push) Successful in 13s
* services/admin-api/internal/attest/pocket_id_token_source.go — attest.TokenSource impl: OIDC discovery via /.well-known/openid-configuration, HTTP Basic client_credentials grant, mutex-guarded in-memory token cache with configurable RefreshLeeway (default 30s = "verwirft bei exp-30s" from DEV-602).
* services/admin-api/internal/attest/pocket_id_token_source_test.go — 10 subtests against an httptest OIDC mock: happy path, cache hold, exp-30s refresh boundary, optional scope/audience, bad creds (401), missing config, discovery failures, non-positive expires_in, type-conformance.
* services/admin-api/cmd/admin-api/main.go — buildTokenSource(): -bearer flag wins (dev), else -oidc-issuer + POCKET_ID_CLIENT_ID + POCKET_ID_CLIENT_SECRET env → PocketIDTokenSource (prod); hard-fail at startup if issuer is set but credentials are missing. New flags -pocket-id-scope / -pocket-id-audience for the client_credentials request.
* services/admin-api/README.md — split inbound/outbound Pocket-ID auth, document the admin-api OIDC client registration (UI + API), new flags, env vars, prod bootstrap example.

36/36 go tests pass, go vet clean.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 18:04:00 +00:00
CTO Agent
d9fa1c55b9 services: add signing-service (real Ed25519 DSSE, in-memory keyring, AllowAll+Static validators as DEV; OpenBao/OPA/Pocket-ID as stubs). Interop with admin-api attest.HTTPSigner verified: 19 Go-Tests + end-to-end content_hash equality.
All checks were successful
validate / Rego (opa fmt + opa test) (push) Successful in 14s
validate / JSON Schema (ajv) (push) Successful in 19s
validate / Go (vet + test) (push) Successful in 18s
2026-10-25 18:02:44 +00:00
CTO Agent
ca62108f70 admin-api: wire Signer+TLog into Create/Retire handlers; on tlog outage row stays attestation-pending (26/26 tests pass, go vet clean)
All checks were successful
validate / Rego (opa fmt + opa test) (push) Successful in 13s
validate / JSON Schema (ajv) (push) Successful in 20s
validate / Go (vet + test) (push) Successful in 19s
2026-10-25 17:51:53 +00:00
CTO Agent
cacdf7e286 attest+tlog: ADR-0005 Signing/TLog API contract, HTTP clients + orchestrator in admin-api (go vet clean, 11 new tests pass)
Some checks failed
validate / Rego (opa fmt + opa test) (push) Successful in 13s
validate / JSON Schema (ajv) (push) Has been cancelled
validate / Go (vet + test) (push) Has been cancelled
2026-10-25 17:51:33 +00:00
CTO Agent
65514559dc services: add admin-api Go service (per-diem rates CRUD, in-memory + Postgres stub store, HTML templates, 13/13 go tests pass, go vet clean)
Some checks failed
validate / Rego (opa fmt + opa test) (push) Successful in 12s
validate / JSON Schema (ajv) (push) Has been cancelled
2026-10-25 17:28:14 +00:00
CTO Agent
3f466dfae8 services: add per-diem-calculator Go skeleton (deterministic compute core + 5 unit tests, HTTP wire stubbed); README reflects new layout
All checks were successful
validate / Rego (opa fmt + opa test) (push) Successful in 11s
validate / JSON Schema (ajv) (push) Successful in 18s
2026-10-25 17:07:55 +00:00