Skip to content

chore(release): promote dev for 0.2.0-beta.4#992

Merged
oscharko merged 63 commits into
mainfrom
dev
Jun 12, 2026
Merged

chore(release): promote dev for 0.2.0-beta.4#992
oscharko merged 63 commits into
mainfrom
dev

Conversation

@oscharko

Copy link
Copy Markdown
Contributor

Summary

Promotes the current dev branch to main for the 0.2.0-beta.4 npm beta release.

Refs: N/A - release promotion.

Scope

  • In scope: all merged dev changes through 1015505, including Local Knowledge workspace connector work, Local Knowledge retrieval hardening, QI hardening, and the 0.2.0-beta.4 version bump.
  • Out of scope: open PRs that still target dev and are not part of current dev.

Reuse And No-Duplication

  • Existing Keiko functionality was inspected before implementation.
  • This PR reuses, extends, generalizes, or consolidates existing functionality where practical.
  • Any new implementation is limited to a documented capability gap in the linked issue.
  • This PR does not introduce a parallel workspace, graph, relationship, policy, evidence, memory, connector, workflow, or UI subsystem where an existing subsystem can be extended.
  • Refactoring or consolidation was considered when existing functionality was close but not shaped for this change.

Delivery Board

  • Linked issue is in the public Keiko Product Delivery project.
  • Linked issue has a valid Parent Epic: #<epic_number> unless this PR updates an epic container directly.
  • Linked issue is attached as a GitHub sub-issue of its parent epic so it appears in the correct board swimlane.
  • Parent epic is on the board with Classification: Epic, Status: Open Epics, and priority/order set for top-to-bottom implementation sequencing.
  • Linked task/card issue is on the board with Classification: Task, Status: In Progress while active, inherited or explicit Priority, and Human Review Required: Yes.
  • Project Status is In Progress while work is active, or Done only after merge and closure evidence.
  • Project Workflow State is PR Open or Ready for Human Review.
  • Owner / Agent, Branch, Pull Request, and Human Review Required are filled.
  • Issue label reflects the current state: status: in progress, status: ready for human review, or status: done after merge.
  • Autonomous agents did not merge into dev, enable auto-merge, close the issue, or bypass human review unless explicitly authorized by the human maintainer.

Product Impact

  • UI or user workflow
  • CLI or developer workflow
  • Core generation engine
  • Evidence, audit, or compliance artifact
  • Security or supply chain
  • Packaging, release, or npm publication
  • Documentation or repository hygiene
  • No user-facing behavior change

Verification

Required:

  • Required GitHub checks pass before merge.
  • Local verification commands or rationale are listed below.
  • Reuse/extension/generalization evidence or gap rationale is listed below.

Local verification:

npm ci
npm run check:version-consistency
npm run typecheck
npm run lint
npm run arch:check
npm run arch:check:negative
npm run check:qi-supply-chain
npm test
npm run build
npm run prepare:bin
npm run build:ui
npm run prune:package-native-optionals
npm run check:package-surface
npm run smoke:install
npm run smoke:install:memory
npm audit --audit-level=high
npm sbom --sbom-format cyclonedx --omit dev
npm run check:workspace-supply-chain
npm sbom --sbom-format cyclonedx --omit dev --workspace @oscharko-dev/keiko-ui

Reuse / gap rationale:

This is the release promotion of the already-integrated dev branch. The local release branch was checked for stale dirty states first; obsolete local Codex Local Knowledge branches/worktrees were removed before promotion.

Select only what applies:

  • UI behavior manually verified or covered by tests.
  • CLI behavior verified with command output or tests.
  • Core logic covered by unit, integration, property, or fixture tests.
  • Security-sensitive change reviewed for trust boundaries, secrets, external calls, and generated artifacts.
  • Supply-chain or package-surface change verified with package, license, lockfile, SBOM, or npm dry-run checks.
  • Documentation or Markdown change verified by the repository link check or a targeted local equivalent.
  • Release-impacting change verified with pnpm run release:check or an explicit rationale.
  • Not applicable items are explained below.

Not applicable rationale:

  • This repository uses npm release gates, not pnpm run release:check.
  • 0.2.0-beta.4 was confirmed absent from npm before publication.

Review And Closure

  • The PR implements only the linked issue scope.
  • Actionable review findings are fixed or explicitly dispositioned.
  • Unresolved review threads are resolved before merge.
  • Checks are repeated after the latest pushed fix.
  • Issue acceptance criteria and closure evidence are updated only where evidence exists.
  • The linked issue records reuse, extension, generalization, or new-gap rationale before closure.
  • Delivery board status is updated before requesting final maintainer review.
  • Use Resolves #<issue_number> only when this PR should close the issue.

Risk Notes

  • This PR is a release promotion. npm publication must use the beta dist-tag, not latest.
  • Two PRs remain open against dev and are intentionally not included in this release because they were not merged into current dev.

Niko4417 and others added 30 commits June 10, 2026 21:10
Co-authored-by: n.vasilopoulos <n.vasilopoulos@itgain.de>
Co-authored-by: oscharko-dev <o.scharkowski@oscharko.de>
Co-authored-by: oscharko-dev <o.scharkowski@oscharko.de>
Co-authored-by: n.vasilopoulos <n.vasilopoulos@itgain.de>
Co-authored-by: oscharko-dev <o.scharkowski@oscharko.de>
…916)

Co-authored-by: n.vasilopoulos <n.vasilopoulos@itgain.de>
Co-authored-by: Oliver Scharkowski <59687448+oscharko@users.noreply.github.com>
Refs #877

Co-authored-by: OpenAI <noreply@openai.com>
…hor matrix (#925)

* fix(epic-177): deny credential dotfiles and harden secret redaction (#177)

Post-closure audit of the connected-context safe-read boundary found
credential config files reachable when a user connects their home
directory — neither deny-listed nor caught by the redactor:

- .s3cfg (secret_key), .boto (gs_oauth2_refresh_token), .gitconfig
  (token), .dockercfg (auth), .envrc (Stripe sk_live_/rk_live_).

Two layers:
- Deny-list: add .s3cfg, .boto, .dockercfg, .gitconfig, .envrc,
  *.tfvars, *.tfvars.json, .terraformrc to DEFAULT_DENY_PATTERNS so the
  files are never discovered, previewed, or grounded.
- Redactor defense-in-depth: add bare `secret_key`; replace the leading
  \b with a (?<![A-Za-z0-9]) lookbehind so underscore-prefixed key-names
  (e.g. gs_oauth2_refresh_token) are caught; add a Stripe key pattern.

Refs #177

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(epic-177): enforce safe-read deny boundary on grounded-ask route (#177)

Audit found the grounded-ask folder path did not re-validate the
resolved workspace root against the deny-list the way the PATCH route
does. A chat whose projectPath fell back to a credential directory could
reach the orchestrator (the IO edge still denied each read, but no clean
boundary existed at the route). Add an explicit deny check in
dispatchFolderAsk that rejects before the runner is invoked, plus a
mutation-robust regression test (status 400, runner never called,
generic message that does not echo the path).

Also: WCAG 3.2.2 — the connected-context audit-evidence link opens in a
new tab; add an sr-only "(opens in new tab)" hint so screen-reader users
are warned of the context change.

Refs #177

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(epic-177): re-anchor connected-context verification matrix (#177)

The post-closure matrix had drifted: 23 file:line citations no longer
pointed at their symbols/tests after downstream edits. Re-anchor every
citation against current dev HEAD (verified by grep) and record the
2026-06-11 post-closure audit pass and its fixes.

Refs #177

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Bumps @types/node from 25.9.2 to 25.9.3.
Fixes the workspace lockfile entry so CI SBOM validation accepts axe-core 4.12.1.
Keep the UI CLI complexity under the configured quality gate and verify launch-cwd workspace behavior.
…tion (#189) (#927)

Audit of Epic #189 against origin/dev surfaced four confirmed production-readiness gaps in the Local Knowledge connector; this hardens them without expanding scope.

- Embed each chunk's own character sub-span instead of re-deriving the full parsed-unit span for every chunk. A multi-chunk unit (e.g. a dense PDF/manual page) previously produced identical duplicate vectors and an unbounded embedding input. Per-chunk offsets are now persisted via schema migration v8 (nullable; chunks indexed before the migration fall back to the parsed-unit span until reindexed, so behaviour is byte-identical for existing capsules).

- Redact secret-shaped excerpt text in the single-connector grounded-ask prompt, matching the redaction the hybrid path already applies (closes the #201 model-prompt redaction deliverable for this path).

- Re-validate the deny-list against the canonical (realpath) source root at index time, not only at connect time, closing a TOCTOU where a connected folder later moved/symlinked into a denied location would still be indexed.

- Stop echoing the raw JSON.parse error message (modern Node embeds a fragment of document text) into the persisted, UI-surfaced parse diagnostic; emit the error type name instead.

Regression tests added for each fix. Gates: typecheck, lint, arch:check(+negative), package-surface, build:ui; contracts 899, local-knowledge 511, server 1808 — all green.

Refs #189

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Restore package-level typecheck gates by removing the invalid tsc build-mode noEmit combination across workspace packages.

Closes #935
Add selected-root file content read/write routes and wire the Files preview into a real editor window.

Include deny-list coverage, conflict protection, and editor-launch safeguards for unsupported previews.

Closes #845
Refs #184

Audit hardening for Files-window connected scopes into Conversation Center: safe stale-scope errors, credential-shaped metadata rejection, plural connectedScopes UI handling, configured grounding limit propagation, and focused regression coverage.
oscharko and others added 29 commits June 11, 2026 17:59
…177 · #189) (#963)

* fix(grounded): fail-soft per-source retrieval for multi-source and hybrid runs

A pack-validation failure on one connected folder, or an embedding-adapter
misconfiguration on one Local Knowledge connector, no longer aborts the whole
grounded run. The bad source is skipped, surfaced as a source-skipped
uncertainty marker, and the answer is built from the remaining healthy
sources. Only when every source fails does the run return the first coded
error. Restores the Epic #532/#729 N+1 fail-soft contract for Release 0.2.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(grounded): reject prompts whose overhead alone exceeds the input limit

promptBudgetedMessages and budgetedMultiSourceGatewayMessages silently
returned over-limit messages when the system prompt + question + framing
already exceeded the model input budget, producing an opaque provider 400.
Both now throw ContextOverflowError, which the existing gateway error
mapping turns into a clean coded response.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): normalize connected roots and stop fabricating the src sentinel

appendScope now trims trailing path separators before dedup so /folder and
/folder/ cannot be connected twice. filesContextFor returns null instead of
the fabricated "src" root when a Files window has no real root, preventing
a spurious "Context src/" chat badge and a stray workspace source in the
QI Generate path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(local-knowledge): restore force re-chunk semantics lost in #196 hardening

Commit 081c7f0 dropped force from the chunkDocument call, so a force
re-index only re-embedded existing chunk rows instead of re-chunking from
source text. Force now re-chunks again while the #196 per-document recovery
path (non-force) keeps reusing intact chunks and only re-embeds missing
vectors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(security): validate figma render URLs and deny-list project roots

Render URLs returned by the Figma /v1/images API are now validated before
download: https only, no IP-literal hosts, no localhost/.local — blocking
SSRF from a compromised or MITM'd API response (skip reason
render-url-blocked). POST /api/projects now applies the same safe-read
deny-list as connected scopes, so credential directories such as ~/.ssh can
no longer be registered as a project root (403 DENIED).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(release): fill README quickstart, fix publish workflow and runbook route

Quickstart now documents the real global flow (keiko start --open, port
1983, keiko stop) instead of an empty section. The publish workflow gains
the missing prune:package-native-optionals step in CI order, and the shell
runbook references /memoriaviva after the MemoriaViva rebrand.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(qi): de-flake ExportBar object-URL revoke assertion

The test asserted revokeObjectURL had NOT been called immediately after the
download click, then asserted it eventually was. That negative assertion
raced the component's 250ms deferred-revoke timer: under heavy parallel test
load the gap between click and assertion exceeded 250ms, so the timer had
already fired and the suite flaked (~1 failure per full run). Drop the
wall-clock-dependent negative assertion; keep the real contract — a blob URL
is created and eventually revoked with the same URL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(grounded): fail-soft when a connected folder root becomes inaccessible

A folder root that was deleted, unmounted, or whose symlink now resolves into
a denied directory between connect and ask no longer aborts the whole grounded
run with a 400. canonicalizeGroundedFolderScopes collects such scopes as
skipped (preserving the original coded reason) instead of returning on the
first failure; handleGroundedAsk dispatches on the effective (healthy) folder
count and only returns the original 400 when no healthy source — folder or
connector — remains. Skipped folders surface as source-skipped uncertainty
markers (basename label only, no path leak) through the multi-source and
hybrid assembly. A denied- or inaccessible-only chat still hard-fails with its
exact safe error, preserving the security boundary.

Live-verified against Azure: 2 connected folders, one deleted before the ask →
HTTP 200 with the answer grounded in the healthy folder plus a skip notice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
POST /api/local-knowledge/capsules/:id/connection created a new randomUUID
source row on every call, so connecting the same folder twice (double-click,
or a trailing-slash/symlink-alias spelling) produced two sources and doubled
every indexed document, chunk, vector, and grounded citation. The handler now
computes a stable, field-ordered identity over the canonicalized scope
(realpath already folds slash/symlink variants) and returns the unchanged
capsule body with 200 when an identical scope is already attached. Scopes
that differ in globs, recursion, or file lists remain distinct sources.

Live-reproduced before the fix: two connects of the same corpus folder
indexed 8 instead of 4 documents.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Check the sandbox UI port before running the refresh/build cycle.

When the default port 1983 is busy, select the next free loopback port and pass it explicitly to keiko ui. For explicit ports from --port or KEIKO_UI_PORT, fail early with a clear message instead of building and then crashing with EADDRINUSE.
…it, Verbindungs-Lebenszyklus, Suche-Robustheit (#142 · #532 · #177 · #189) (#968)

* fix(server): enforce the combined 16-source cap at write and ask time

Release 0.2.0 acceptance: "up to 16 sources" is a TOTAL across folder/
file/repo scopes AND knowledge connectors, but the store only bounded
each list independently (16+16), so a chat could hold 32 sources and
the 17th was never prevented. QI already capped the combined total at
16 (MAX_QI_SOURCES) — Chat now matches:

- store: validateTotalSourceCap rejects any patch that GROWS the
  combined total past max(maxConnectedSources, maxLocalKnowledgeSources)
  (default 16). Growth-only: over-cap legacy rows (written under a
  higher operator limit) may shrink or hold, so nobody is locked out.
- ask path: a stored over-cap chat no longer fans out unboundedly.
  The hybrid path caps both lists via capSourcesToLimits; the pure
  multi-folder path caps in dispatchFolderAsk. Over-cap sources surface
  as source-skipped uncertainty entries (basename label, no path leak).

Refs #142 #532 #189

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): reject the over-limit connect gesture and unbind what was bound

Four user-visible source-connection defects in the Conversation Center:

- appendScope/appendConnectorScope silently EVICTED the oldest source
  when a 17th was connected (slice(-max)), leaving its edge dangling.
  At the cap they now return the list unchanged; AppShell rejects the
  gesture with a visible role="alert" notice ("Source limit reached —
  N of 16 connected"), and confirmConnect skips drawing the edge when
  the bind callback vetoes (no edge without grounding).
- ScopeConnectButton fired a doomed PATCH at the cap and surfaced the
  raw backend error. It now reads effectiveGroundingLimits, becomes
  aria-disabled with a focusable limit hint, and short-circuits the
  round-trip (update-in-place of an existing identity stays allowed).
- removeScope compared roots verbatim, so a trailing-slash spelling
  difference made disconnect a silent no-op. It now compares
  normalised roots (mirror of the appendScope dedup).
- removeConn/closeWithTeardown re-derived the unbind target from the
  window's CURRENT cfg — after the user navigated the Files window or
  re-selected another capsule, the WRONG source was unbound. Edges now
  snapshot boundRoot/boundConnector at bind time (persisted, type-
  checked on reload) and unbind from the snapshot, with cfg-derivation
  as fallback for pre-0.2.0 edges.

Refs #142 #532 #189

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(search): degrade unreadable files to skips and yield during scans

The fast content search promised "a single unreadable file must degrade
to a skipped excerpt, never crash the whole grounded answer", but an IO
error (EACCES/ENOENT after a TOCTOU permission change) thrown by
probeBinary or the scan read escaped both the scan path and the excerpt
path and surfaced as an HTTP 500:

- scanFile/readForScan: IO errors (Node errno shape) now become
  tool-unavailable skip candidates; programmer errors still throw.
- readExcerpt: probeBinary IO errors re-classify to the existing
  RepoSearchUnsupportedFileError skip ("io-error"); TypeError rethrows.
- runScanLoop yields to the event loop every 64 files so a large or
  cold-cache folder scan no longer blocks the server while scanning
  (the initial sync directory walk is unchanged and documented).

Refs #177

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(local-knowledge): order-independent files identity + busy_timeout

Two hardening fixes on the capsule pipeline:

- #964 follow-up: scopeIdentity serialized the files array unsorted,
  so connecting the same logical file SET in a different order created
  a second source row and double-indexed every document. The identity
  now sorts a copy of the files list; include/exclude globs stay
  order-sensitive on purpose (ordered match semantics).
- the knowledge store set no busy_timeout, so a concurrent indexing
  write and a grounded-ask audit INSERT could fail immediately with
  SQLITE_BUSY (HTTP 500). LK_STORE_BUSY_TIMEOUT_MS=5000 now mirrors
  the UI store (UI_DB_BUSY_TIMEOUT_MS, issue #639).

Refs #189

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): surface stream failures instead of failing silently

The streamUngrounded .catch path (client-side network failure, e.g. a
connection drop mid-stream or a reader.read() TypeError) resolved
"failed" without setError — no visible error, the send button just
re-enabled — and left the optimistic user message orphaned in the list.
The catch now mirrors the buffered and grounded error paths: setError
with the same redacted formatting, remove the optimistic message.
AbortError (user cancel) is unchanged. Note: the server persists the
user message before streaming (#152), so after a MID-stream drop the
message legitimately reappears on reload — same trade-off as the
other paths, documented at the cleanup site.

Refs #142

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: grounding limits, combined source cap, search-vs-capsule guide

README additions for the 0.2.0 release surface:

- Configuration: the grounding block / KEIKO_GROUNDING_* env vars with
  defaults and hard ceilings, the combined per-chat source cap
  (max(maxConnectedSources, maxLocalKnowledgeSources), default 16) and
  its enforcement points, and GET /api/config effectiveGroundingLimits.
- Core workflows: when to use the fast folder/file search (plain-text
  formats only, content-based binary detection, 2 MiB per-file cap,
  coverage notices) versus Knowledge Capsules (parsed PDF/DOCX/JSON/
  CSV/HTML, persistent embeddings index, reusable across chats,
  capsule sets).

Refs #142 #177 #189

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(server): actionable message when the planner asks for clarification

Asking a broad question over a connected folder surfaced the raw
planner reason in the composer — "clarification needed: too-generic
(BAD_REQUEST)" — which tells the user nothing about what to do next
(live-reported during release testing). The planner already produces
suggestedQuestions; the 400 mapping now explains what is missing and
folds in up to two of them:

  "Your question is too broad to search the connected sources.
   Mention a concrete file name, identifier, or exact phrase so Keiko
   knows where to look. For example: …"

The ClarificationNeededError message itself keeps the stable machine
form for logs; only the wire message changes (both the single-folder
and the multi-source mapping points).

Refs #142 #177

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Hardens external source binding, fast search filtering, local knowledge grounding prompts, and UI connection stability for Keiko 0.2.0.
…cycle-Bounds, Drift, Layout-Fidelity (Epics #750 u. a.) (#973)

* fix(cli): allowlist FIGMA_ACCESS_TOKEN in local .env loading

The .env loader only imported KEIKO_* names, so the documented Figma
connector contract (#751: PAT configured via .env, server-side only)
was unsatisfiable without a shell-level export — the exact operator
trap the 0.2.0-beta audit hit. Closed allowlist, no blanket loading.

Refs #750 #751

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(figma): correct egress error attribution and harden transport ports

Issue #884's root cause: in a no-proxy runtime every direct network
failure classified as FIGMA_PROXY_UNREACHABLE/FIGMA_PROXY_EGRESS_FAILED
with proxy-blaming remediation. FIGMA_PROXY_* is now reachable only via
OutboundHttpEgressError (a proxy genuinely in play); direct failures get
new codes FIGMA_NETWORK_UNREACHABLE / FIGMA_EGRESS_TIMEOUT /
FIGMA_EGRESS_FAILED (content-free messages), plus distinct
FIGMA_PROXY_AUTH_REQUIRED / FIGMA_PROXY_BLOCKED_BY_POLICY instead of a
folded generic code. Ports gain per-request timeouts (60s default),
capped JSON/render reads (10/32 MiB, FIGMA_RESPONSE_TOO_LARGE) and
redirect:manual so the PAT can never follow a cross-origin redirect.

Refs #750 #758 #759 #760 #802 #884

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(quality-intelligence): apply fair per-source byte budget to figma-snapshot ingestion

The figma-snapshot source ignored the #729 fair byte split and always
consumed the full capsule ceiling, so figma + any second source could
exceed MAX_PROMPT_BYTES and hard-fail the whole run with
QI_PROMPT_TOO_LARGE. Budget now threads ingestOne -> ingestFigmaSnapshot
-> figmaScreenDocs exactly like the capsule path; the previously
mutation-blind fair-budget test now fails against the pre-fix code.
Also: replace literal NUL bytes in sourceMixPlanning.ts with \u0000
escapes (runtime-identical, file no longer binary to grep/diff).

Refs #729 #754

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): harden Figma snapshot window error paths, cancel, revoke and a11y

- Load-failure keeps the Load button mounted (retry affordance, focus
  preserved); result section renders whenever a summary exists so a
  failed re-snapshot no longer orphans the previous result; Re-snapshot
  uses the aria-disabled guard pattern.
- PAT revoke (#758) reachable: revokeFigmaToken() + two-step confirm in
  the scopes details block.
- AbortController threading + Cancel during build + unmount cleanup.
- Stale cfg.snapshotRunId cleared on FIGMA_SNAPSHOT_NOT_FOUND so the QI
  hub stops feeding dead run ids.
- Error taxonomy: per-family remediation (proxy wording only for
  FIGMA_PROXY_*, CA wording for TLS, neutral network/timeout wording for
  direct-egress codes incl. new FIGMA_NETWORK_UNREACHABLE/
  FIGMA_EGRESS_TIMEOUT/FIGMA_EGRESS_FAILED); FIGMA_UPSTREAM_UNAVAILABLE
  gets an accurate outage notice; assertive alert moved out of the
  polite status region (double-announcement fix); it.each coverage over
  all egress codes + rejection tests for load/codegen.

Refs #750 #756 #758 #884

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(model-gateway): harden outbound egress — timeouts, fail-closed env config, Host header, size caps

- gatewayFetch gains timeoutMs (composed AbortSignal on direct, CA-fallback
  and proxy paths incl. CONNECT/socket phases -> PROXY_UNREACHABLE on
  stalled CONNECT) and maxResponseBytes override for the streamed paths.
- Env-sourced egress config no longer fails open: the four egress fields
  parse independently, one malformed var (e.g. credentialed HTTPS_PROXY)
  warns by NAME only and no longer silently discards proxy/NO_PROXY/CA
  bundle — the enterprise misconfiguration no longer bypasses the proxy.
- HTTPS-proxy tunnel no longer sends 'Host: host:443' (SigV4-safe for
  pre-signed S3 render URLs).
- Unreadable CA bundle paths warn once instead of degrading silently.
- Test matrix: CONNECT 407/403/5xx codes, parseProxyUrl rejects,
  mapProxyError, NO_PROXY rule forms, timeout aborts, Host header, caps.

Refs #802 #884

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(quality-intelligence): bound and sanitize the Figma extraction pipeline

- deriveNavFlows: MAX_NAV_FLOWS=500 cap + coverage notice (dense boards
  previously enumerated acyclic paths factorially — 12 fully-connected
  screens = 773k items — hanging QI ingestion).
- MAX_TREE_DEPTH=512 guards on every recursive walk (prune, normalize,
  links, tokens, a11y, persisted-IR parse): deep trees degrade instead of
  RangeError, truncation surfaces in reduction stats.
- Determinism: locale-dependent localeCompare sorts replaced with the
  code-unit comparator (cross-host byte-identical output).
- URL prototype actions no longer vanish silently (unresolvedLinks).
- Traceability: baseline lines carry [node:<id>], generated HTML carries
  data-node-id/data-screen-id.
- Codegen artifact safety: screen filenames sanitized (':'/';' invalid on
  Windows; INSTANCE ids parsed as URI schemes in hrefs -> './' prefix),
  tokens.css values validated/escaped (CSS injection via fontFamily).
- Literal NUL bytes in sources replaced with \u0000 escapes (files were
  binary to grep/diff/CI text scanners).

Refs #750 #752 #755 #811 #812

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(figma): bound the snapshot-build request lifecycle and test the HTTP boundary

- In-flight coalescing keyed by fileKey:nodeId — concurrent POSTs join
  one governed build, one runId, one persist (double-submit no longer
  multiplies Figma load into mutual 429s).
- KEIKO_FIGMA_BUILD_DEADLINE_MS (default 600s) bounds the request via a
  504 FIGMA_BUILD_TIMEOUT; persist runs inside the build chain so a
  deadline-expired build still stores its record for retry recovery.
- Client disconnect unblocks the waiter promptly (req close -> race);
  the coalesced build keeps serving other waiters.
- KEIKO_FIGMA_REQUEST_TIMEOUT_MS threads per-fetch timeouts into both
  transport ports (GovernedSnapshotDeps.portOptions).
- Route status mapping covers the new egress taxonomy (502 family) with
  content-free per-code operator messages.
- NEW figmaSnapshotRoutes.test.ts: 41 tests through the REAL server
  harness (CSRF 403, Content-Type 415, FIGMA_BAD_LINK matrix incl.
  evilfigma.com, full code->status matrix, deadline 504, env parsing,
  coalescing, revoke, persist-failure 500) — this boundary had zero
  tests and previously shipped the codegen 415 blocker unseen.

Refs #750 #759 #760 #884

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(figma): render-egress attribution, SSRF normalization, snapshot-store integrity and retention

- Render downloads no longer swallow coded egress errors into anonymous
  skips: hard egress failures (TLS/proxy family) abort the build with
  their code; transient failures skip with render-fetch-failed:<CODE> so
  an all-skipped snapshot is distinguishable from flakiness.
- Render-URL guard normalizes hostnames (trailing dot, case), blocks the
  .localhost reserved TLD and restricts to default https port — closes
  the live-verified localhost. bypass.
- Store load() recomputes the snapshot integrity hash and rejects
  tampered/truncated records (codegen fixture now persists a genuinely
  recomputable hash); listByScope() exposes board-stable history for
  drift detection; record-write failure cleans up side-files and a lazy
  sweep removes orphaned side-dirs/tmp files; figma snapshots gain
  retention enforcement mirroring the #274 record-first discipline.

Refs #753 #758 #760 #963

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(quality-intelligence): preserve layout fidelity in the Figma Screen-IR and generated code

The IR previously dropped all layout/style binding (auto-layout
direction, padding, itemSpacing, sizing, cornerRadius, per-TEXT
typography), so generated code had no visual fidelity and tokens.css
variables were never consumed. IrNode gains four OPTIONAL, hash-neutral
fields (layout/sizing/cornerRadius/typography) projected defensively in
normalize.ts; htmlCssAdapter emits a deterministic per-screen <style>
block (display:flex, flex-direction, gap, padding, border-radius,
width:100%/flex:1 for fill sizing) and references tokens.css variables
(var(--font-N)) when node typography matches a token. Hash neutrality
is regression-tested: existing snapshot records keep their integrity
hash. Artifact header documents what is and is not reproduced.

Refs #750 #752 #755

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(quality-intelligence): make drift detection real for figma-snapshot sources

A figma-snapshot source pins a write-once, content-addressed runId, so
re-checking the pinned record could never report drift: change the
board, re-snapshot, click re-check -> 'all fresh' (false assurance).
The drift loader now resolves the LATEST snapshot recorded for the same
board scope (fileKey+nodeId via the store's listByScope) and ingests it
for comparison whenever its integrity hash differs from the pinned
record; screen atom ids are board-stable (screenId-derived) so
compareStaleness reports changed atoms, and regenerate-stale re-ingests
the latest snapshot. The generate path keeps exact pinning.

Refs #735 #754

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(figma): operator documentation and ADRs for the snapshot connector and egress layer

- README: Figma connector env table (FIGMA_ACCESS_TOKEN allowlist,
  KEIKO_FIGMA_* dials incl. the new request-timeout and build-deadline),
  coded-error overview, and an outbound-egress section (keiko.config.json
  egress block, KEIKO_*_PROXY/NO_PROXY/CA_BUNDLE_PATH precedence, trust
  composition incl. OS trust store).
- ADR-0037: Figma snapshot as the only communication boundary (PAT-only,
  per-screen BFS budgets, immutable integrity-hashed evidence,
  deterministic model-free pipeline, board-stable drift identity).
- ADR-0038: shared proxy/custom-CA egress seam (#802) with fail-closed
  env parsing and proxy-attribution discipline (#884).
- model-gateway: keep parseEnvEgressConfigFaultTolerant out of the
  public barrel (package-surface contract).

Refs #750 #802 #884

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(quality-intelligence): re-hydrate layout-fidelity fields from persisted irJson

Live integration caught parseScreenIr dropping the new optional
layout/sizing/cornerRadius/typography fields when re-hydrating a STORED
snapshot for codegen — the in-memory emitCode tests were blind to the
persisted round trip, so generated code from real snapshot records
silently lost its <style> block and token references. Defensive total
parsers added (malformed values drop per-field, pre-existing records
without the fields stay valid) + a persist->parse->emit regression test.

Refs #752 #755

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(model-gateway): pin the fake forward-proxy to its local origin

CodeQL flagged the test harness's blind req.url forward as
js/request-forgery (critical). The fake proxy now forwards to the known
local origin only — same assertions, no attacker-controllable URL flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): drop dead combined egress-error set and unused test import

The per-family remediation branches (proxy/CA/network) made the combined
FIGMA_EXTERNAL_DEPENDENCY_ERRORS set unreferenced; github-code-quality
flagged it and the unused 'within' import on PR #973.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ge gaps (#362) (#974)

Audit follow-up for the #362 reuse-baseline preflight. Since the Epic #270
merge, later epics added QI source under new subdirectories that the
point-in-time guards no longer fully covered. Tighten the gates so the
ADR-0023 D4/D14 independence and pure-domain invariants stay enforced:

- keiko-evidence purity guard: scan production sources recursively (skip
  __tests__), key IO allow-list by relative path so figmaSnapshot/store.ts
  is distinct from the top-level store.ts, and pin the full file list so a
  new file cannot slip the scan. figmaSnapshot/schema.ts is now scanned.
- keiko-quality-intelligence purity guard: expand scan root from src/domain
  to all of src/; match node: specifiers by quoted prefix (ignores backtick
  purity comments) and bareword core modules by closing-quote-anchored regex
  across from/require/import forms, eliminating the prose over-match. Add
  negative + positive regression tests.
- severity-gate: pin direction variants 3e-3l and 10a at "error" so a future
  PR cannot silently soften an extracted-package boundary.

Docs: name ADR-0023's successor (ADR-0025) and fix its D7 runtime-state-
contract reference; anchor the condensed parity matrix as a point-in-time
record with a link to the full historical gate.

Refs #362

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…are side-effect patterns (#975)

Audit follow-up for Issue #363. Brings the scoped src/hardening independence guard to 8-pattern parity with the package-wide guard (adds the two bare side-effect import forms missed in PR #490 DOD-8). Test-only, all gates green. Refs #363
…ote (#271) (#976)

Audit of the Issue #271 deliverable (ADR-0023 Quality Intelligence
migration architecture). The migration map and anti-duplication invariant
are sound — arch:check, arch:check:negative, and check:qi-supply-chain are
green, no production source imports @oscharko-dev/test-intelligence or
@oscharko-dev/ti-*, and keiko-quality-intelligence is a pure-domain package
(runtime deps = keiko-contracts + keiko-security only).

Four reused-API references in the map named Keiko functions that never
existed under those names (confirmed: 0 commits ever defined them), which
undermines the "reuse-as-is" evidence implementors are told to cite:

- discoverWorkspace()        -> detectWorkspace()        (keiko-workspace)
- buildWorkspaceContextPack() -> buildContextPack()      (keiko-workspace)
- retrieveLocalKnowledge()    -> runLocalKnowledgeRetrieval() (keiko-local-knowledge)
- discoverCapabilities()      -> listConfiguredCapabilities()  (keiko-model-gateway)

Also replace the stale D14 rule-count note: it referenced an EXPECTED_RULES
scalar that no longer exists and stated a total of 32 dependency-cruiser
rules (live count is 31). Restate it against the live mechanism
(scripts/arch-check-negative.mjs EXPECTED_DEPCRUISER_RULE_COUNTS) without a
drift-prone count.

Documentation-only; docs/adr/ is prettier-ignored. ADR-0023 remains the
superseded historical Epic #270 record.

Refs #271
… and parity matrix (#362, #285) (#977)

Follow-up to the Issue #271 audit (PR #976), which fixed the same drift in
ADR-0023. Two historical QI documents cited Keiko API function names that
never existed under those names (confirmed: `git log -S<name> --
'packages/**/src/**'` returns 0 commits). Corrected to the real exports so
the "reuse-as-is" evidence resolves:

docs/historical/quality-intelligence-keiko-baseline.md (#362):
- discoverWorkspace()         -> detectWorkspace()            (keiko-workspace/src/detect.ts:172)
- buildWorkspaceContextPack() -> buildContextPack()           (keiko-workspace/src/contextPack.ts:121)
- summarizeWorkspaceContext() -> buildWorkspaceSummary()      (keiko-workspace/src/summary.ts:36)
- retrieveLocalKnowledge()    -> runLocalKnowledgeRetrieval() (keiko-local-knowledge/src/retrieval/index.ts:6)
- discoverCapabilities()      -> listConfiguredCapabilities() (keiko-model-gateway/src/model-selection.ts:62)

docs/historical/quality-intelligence-parity-matrix.md (#285):
- retrieveLocalKnowledge()    -> runLocalKnowledgeRetrieval()

Names-only correction; every replacement verified to resolve to a real
export. The approximate `index.ts line ~NN` citations are left as-is: they
are explicitly hedged pointers to the package barrel and re-pinning shifting
line numbers in a frozen historical record would add churn without accuracy
benefit. Both files remain Prettier-conformant.

Refs #362
Refs #285
…rect enforcement docs (#286) (#978)

Audit of Issue #286 (preserve standalone Test Intelligence compatibility during
the native migration) against current dev surfaced four confirmed gaps in the
TI-import independence enforcement and its documentation.

- HIGH: a dynamically-assembled package name — a template-literal dynamic import
  or require that interpolates a variable right after the @oscharko-dev/ scope —
  bypassed every guard, because the supply-chain gate and all regex guards rely on
  the contiguous literal never being split. Add a dynamic-scope pattern to
  scripts/check-quality-intelligence-supply-chain.mjs (the repo-wide CI gate) and
  to the keiko-quality-intelligence package + hardening guards. The negated class
  keeps the match inside the package-name segment, so dynamic SUBPATHS of
  statically-named packages stay allowed (verified: no false positives on dev).
- LOW: the root package.json peerDependencies section was not scanned for
  forbidden TI dependencies; add it.
- MEDIUM: the compatibility contract claimed the "zero TI imports" invariant was
  enforced by arch:check rule direction-10a. That rule only constrains keiko-*
  import direction and never matches @oscharko-dev/test-intelligence; the operative
  gate is check:qi-supply-chain. The old verification command
  (arch:check | grep direction-10a || true) could never fail. Correct sections
  2/5/7/9 to point at the real gate and fix the under-scoped grep commands.
- LOW: the still-governing contract lived under docs/historical/ with no status
  marker; add a "governing, not archived" banner (AC1 discoverability).

Regression tests cover the dynamic-evasion form (positive + negative) in both the
supply-chain harness and the regex guards.

Refs #286

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…upply-chain gate (#287) (#979)

The Issue #287 supply-chain gate enforced only two directions (approved-runtime
present, denied absent) and never failed on a runtime dependency that had no
matrix row at all. Three external runtime deps already shipped in the published
@oscharko-dev/keiko graph without a governance row — pdfjs-dist, yauzl,
@types/yauzl (via the bundled keiko-local-knowledge) — plus the @napi-rs/canvas
optional. Audit of origin/dev against the #287 acceptance criteria found:

- AC4 completeness (HIGH): add a fail-closed check that every external dep in the
  published runtime surface (root + bundled-package dependencies/
  optionalDependencies) maps to an approved-runtime matrix row. @oscharko-dev/*
  workspace packages and @types/* declaration-only stubs are exempt. Document
  pdfjs-dist, yauzl, and @napi-rs/canvas (pruned) as approved-runtime rows.
- optionalDependencies bypass (MEDIUM): optionalDependencies were invisible to
  the deny-list, telemetry, and name scans — now scanned in every section.
- AC1 license (MEDIUM): the decision matrix gains a license column and the gate
  fails closed if any approved-runtime/approved-dev row omits a license.
- defer-to-decision (LOW, latent): documented as "treat as denied" but enforced
  as a no-op — now enforced as denied (must be absent until promoted).
- native addons (LOW): the package-surface gate now rejects any *.node binary
  generically (rule set extracted to scripts/package-surface-rules.mjs so it is
  unit-testable), not only @napi-rs/canvas by name.

+116 gate regression tests (completeness, defer-to-decision, license,
optionalDependencies, native-addon rules, plus mutation-survivor gap-fills).
Extend the test-file max-lines-per-function eslint exemption to .test.mjs.

Verified vs origin/dev: typecheck, lint, arch:check(+negative),
check:version-consistency, check:qi-supply-chain, and 7358 tests all green.

Refs #287

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rden QI contract tests (#277) (#980)

Residual-gap audit of the Issue #277 Quality Intelligence contract surface
against current dev. Confirmed gaps fixed; contracts-only, no behavior change
for any production caller.

- looksLikeBrowserSafeSourceEnvelope did not enforce its documented promise: it
  ignored provenance.origin and connector adapterId, and missed non-http URL
  schemes, credential shapes, and absolute filesystem paths. It now scans all
  four display fields (displayLabel, localRef, provenance.origin, adapterId)
  for any scheme://, well-known credential token shapes, and absolute paths
  (AC1). The guard has no production callers, so this is regression-free.
- validateQualityIntelligenceIdString accepted leading/trailing whitespace,
  which causes silent id round-trip key mismatches; surrounding whitespace is
  now rejected (spec: non-empty after trim).
- packageSurface test now pins the EXACT public value-export set instead of a
  subset, and extends the pure-leaf import deny-list with node:path, node:url,
  node:crypto, node:os, node:process, node:stream, node:worker_threads
  (AC4 + ADR-0019 leaf purity).
- Added an evidence-atom redaction-metadata fixture plus round-trip and
  hasCanonicalSha256Hash behavioral tests (serialization + redaction-metadata
  test deliverables).
- compatibilityRoundTrip now asserts the exact run-event kind sequence, and
  added empty/single/gap monotonic-acceptance tests (event-ordering deliverable).
- Added a programmatic secret/PII scan across every QI fixture (Audit Addendum:
  fixtures must be synthetic and sanitized).
- Corrected a misleading "subpath barrel" comment in the contracts barrel that
  referenced a non-existent subpath export.

Refs #277

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…arden #272 domain golden tests (#981)

* fix(quality-intelligence): correct trivial-contradiction parity and harden #272 domain golden tests

Audit of #272 (port core test-design logic into the approved Keiko domain
seam) against origin/dev surfaced one correctness defect plus several
under-proven acceptance criteria in the ported pure-domain layer.

- validation: checkTrivialContradictions stripped negation from both the
  precondition and the expected result and only fired when the result was
  negated. This produced a false positive (two identically-negated lines
  flagged as a contradiction) and a false negative (a negated precondition
  versus a positive result missed). Replace with XOR negation-parity over
  equal negation-stripped cores. The true-positive finding id, ordinal, and
  severity are unchanged, so evidence semantics are preserved.
- deduplication tests: prove the documented order-independence guarantee
  (the lexicographically smallest id survives even when it appears second)
  and that riskClass and expectedResults are equivalence-signature inputs.
- validation tests: assert exact finding severities and add false-positive
  and false-negative contradiction regressions.
- pipelineParity (new): exercise the previously unreferenced insurance
  fixture and assert deterministic, idempotent end-to-end parity across all
  three representative domain fixtures (AC#3).
- purityGuard: extend the denied node builtin list with the remaining
  capability-bearing modules (vm, worker_threads, cluster, dgram, http2,
  inspector, module, v8) per the package-graph addendum.

Refs #272

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(quality-intelligence): use exact PolicyProfile type in pipeline parity test

The package tsconfig excludes **/*.test.ts, so `tsc -b` never type-checked the
new pipelineParity test; only the root `tsc --noEmit` (exactOptionalPropertyTypes)
did, and it failed CI. The local `PolicyProfile` alias derived from
`Parameters<typeof deriveIntent>[1]` resolved to `PolicyProfile | undefined`,
which is not assignable to the optional `profile?: PolicyProfile` field of
DesignTestCaseCandidatesInput under exactOptionalPropertyTypes. Import the
exported `PolicyProfile` interface directly; callers only pass concrete profiles.

Refs #272

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Stabilize workspace chat grounding flows

* Harden PDF indexing test timeout
…, label safety, and prompt normalisation (#983)

2nd post-closure re-audit of Issue #278 (migrate source ingestion through Keiko
Workspace/Connector boundaries). A read-only audit fleet (explorer, architect,
security-auditor, pr-reviewer) confirmed five residual gaps; the core STOP-condition
invariants (path containment, deny rules, no Figma live contact, fail-closed
connector auth, read-time redaction) were verified sound.

- figma-snapshot ingestion now emits the dedicated `figma-evidence` source-envelope
  kind instead of `repository-context`, so citations, source-mix priority, and any
  kind-grouped audit rollup attribute it to its Figma origin (AC2 explicit
  connector-backed source + AC4 citation/audit attribution).
- sanitiseLabel strips ANY URL scheme (not only http(s)), well-known credential
  token shapes, and collapses an absolute-path label to its basename so a connected
  source's display label can never echo a secret, URL, or filesystem path into the
  browser-surfaced envelope (#277/#278 display-surface invariant).
- scrubEvidenceText additionally strips invisible zero-width / bidi-override format
  controls (which NFKC does not remove) before model-prompt construction
  (#278 Audit Addendum: normalise untrusted content before prompt/render/export).
- normaliseUntrustedContent keeps TAB/LF/CR as legitimate text whitespace instead
  of collapsing multi-line content into a single run-on line, fixing a latent
  corruption in the reserved ADF pure-domain seam; the mutation-blind whitespace
  test is replaced with real preservation assertions.
- the historical parity matrix marks the Jira ADF parser / `connector-document`
  envelope kind as a reserved pure-domain seam (live ingestion deferred to #283),
  not a wired #278 capability, correcting a false-positive in the audit record.

Tests: +figma kind assertion, +zero-width/bidi scrub, +TAB/LF/CR preservation.
QI suites green (1118 + 147), typecheck / eslint / arch:check / qi-supply-chain
green. package-surface requires build:ui (validated in CI).

Refs #278

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d extend gateway-bypass guards (#279) (#984)

Post-closure re-audit of Issue #279 (route QI model-assisted generation and
judges through the Keiko Model Gateway). Confirmed and fixed gaps against the
acceptance criteria; behaviour-preserving on the success path.

- AC3 leak (HIGH): safeReasonSummary echoed the raw first line of any non-QI
  error into the stage:failed / run:failed SSE reasonSummary. A QI generation
  model.call rejects through the gateway with an error whose message can carry
  the provider base URL, deployment endpoint, or a credential-shaped substring
  (cf. conversation-audit.test.ts). Make it fail-closed: QI safe-errors surface
  their qi/* code, allow-listed in-repo QI errors surface their QI_* code or
  name, every other (gateway/provider/unexpected) error collapses to a generic
  "qi-run-error" with no message echo. The persisted-manifest vector was already
  safe (store redacts every leaf; reasonSummary is not persisted).
- AC3 defence-in-depth: route the QI SSE reasonSummary (the only free-text event
  field) through deps.redactor in toStreamEvent, mirroring the Conversation
  Center SSE redaction posture.
- AC3 parity: judge scrubCandidateText now strips zero-width / bidi invisible
  format controls, restoring parity with generationPort scrubEvidenceText (which
  gained them in #983).
- STOP-condition gate: add provider-SDK bypass guards over the keiko-server and
  keiko-workflows QI dirs (where the live model.call sites are), not just the
  gateway submodule.
- AC1 coverage: capability-gate tests for the function-calling and vision
  mismatch branches; judge fail-before-network assertion.
- Doc: clarify the dispatcher + budget/cache/circuit primitives are a reusable
  ported-from-Test-Intelligence composition, not the live path (which inherits
  resilience from the gateway layer); they must not be force-wired or deleted.

Refs #279

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eway-call audit count, and persist redaction (#273) (#985)

Post-closure re-audit of Issue #273 (Quality Intelligence workflows executed
through Keiko Harness and Workflows). Three confirmed gaps fixed, plus the
deliverable harness test suite (lifecycle, cancellation, limits, event
ordering, failed-run recovery).

- runEntries: the scripted qi:test-design entry emitted an undeclared "intent"
  stage, so withStage -> assertStageRegistered threw and the run could never
  reach a succeeded terminal state. Derive intent inside the declared
  "candidates" stage so the scripted entry stays a strict subset of the shared
  descriptor's stage set (AC#1 consistent terminal states).

- modelRoutedTestDesign: a failed generation gateway dispatch was not counted
  (modelGatewayCallCount was incremented only after a successful await), so a
  failed run persisted a count of 0 despite a billed gateway call. Count the
  attempted dispatch on throw, mirroring the judge contract. The deterministic
  baseline port never rejects and reports 0, so it is unaffected (AC#1 evidence
  integrity).

- evidence/store: coverageMatrix (requirementExcerptRedacted, derived from raw
  source text) and modelParameters (a free-shaped Record) bypassed the
  persist-time redactor, contradicting the "persist redacts every leaf" backstop
  claim. Route both through redactQualityIntelligenceEvidence; redaction is
  idempotent on clean data so existing manifest integrity hashes are unchanged
  (AC#3 audit-storage safety).

- tests: descriptor<->entry contract guard (catches the undeclared-stage class),
  scripted-entry lifecycle/ordering/failed-run-recovery/limits, end-to-end
  cancellation (settles cancelled, persists no manifest), generation
  gateway-call audit regression, and persist-redaction regression.

Refs #273

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on (#274) (#986)

Re-audit of closed Issue #274 (persist QI artifacts through Keiko Evidence and
local state). A read-first 3-lens audit (architect + security-auditor +
pr-reviewer) confirmed AC4 was incomplete: deleteQualityIntelligenceRun removed
only <runId>.qi.json (plus an optional side-file dir) and orphaned the
always-present <runId>.candidates.json companion (customer-derived generated
test bodies) plus server-owned companions in the same qi/ dir. A "deleted" run
left a full reconstruction of the test design on disk.

- Complete the deletion contract: deleteQualityIntelligenceRun now sweeps the
  run's companion artifacts. Evidence-owned (.candidates.json) are always swept
  when evidenceDir is supplied; higher layers pass server-owned suffixes via the
  new companionSuffixes option (keiko-evidence must not hard-code suffixes it
  does not own). Removed suffixes are recorded on the receipt + audit event so
  the deletion is provably complete for compliance.
- Exact-suffix matching (never startsWith): a non-leading '.' is a legal runId
  character, so run-1 and run-1.2 coexist and a prefix sweep would cross-delete.
  Reuses the realpath-contained, symlink-refusing companion-delete primitive.
- Harden removeSideFileDirIfPresent with a lexical containment assertion (the one
  #274 FS op that did not previously route through the containment discipline).
- Correct the misleading "quarantine handles it" comment in handleListQiRuns:
  corrupt manifests are skipped (fail-closed); quarantine is a separate step,
  intentionally not run from a GET.
- Document the integrity-scope (run-level scalars unhashed) trade-off precisely
  in store.ts.
- ADR-0023 D8 + #274 deferred-items: record the companion-deletion contract and
  the explicit deferral of the DELETE route / retention sweep / quarantine wiring
  to the consuming orchestration epic, so AC3/AC4 orchestration is not falsely
  considered met by the library alone.
- Tests: companion removal, dotted-runId collision-safety, server-suffix
  passthrough, receipt accounting, in-memory no-op.

Refs #274

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Improve Local Knowledge Connector workspace integration, capsule drag-to-workspace behavior, chat connector support, indexing/retrieval feedback, embedding retry handling, and deleted-capsule cleanup UX.

Verified by local targeted tests/typechecks/lint, root lint against the PR-scoped tree, and passing GitHub CI/CodeQL checks.
… re-audit (#988)

Post-closure re-audit of #280. Arch-gate UI→QI/LK boundary, run cancel/completion lifecycle (AC2), progressive rendering of growable artifact lists, and accessibility hardening. 1514 keiko-ui tests green; ci + ui + arch checks green. Refs #280
Release version bump for npm beta 0.2.0-beta.4.
@oscharko oscharko merged commit d2d5ef9 into main Jun 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants