feat(ka-routes)!: remove /api/assertion — unify everything on /api/knowledge-assets#1039
feat(ka-routes)!: remove /api/assertion — unify everything on /api/knowledge-assets#1039branarakic wants to merge 11 commits into
Conversation
…y + add wm/quads First increment of the assertion→knowledge-assets unification (issue: remove /api/assertion entirely; base integration/v10-devnet; targeting rc.17). Brings the EXISTING /api/knowledge-assets/* routes to full parity with the legacy /api/assertion/* handlers, fixing the ambiguity bugs the dual-route model caused (PR #971/#988 reviews): - contextGraphId resolution/validation on every mutation (resolveRequiredWrite ContextGraphId) — bad/foreign id is a 400, not an opaque 500 - :name decode + validateAssertionName on all verbs + GET - side-effects restored: emitMemoryGraphChanged + recordAssertionActivity + notification SSE on create/write/finalize/discard/share/publish (dashboards and the bell pane were going silent under the KA routes) - full finalize payload (assertionUri/authorAddress/schemeVersion/chainId/ kav10Address) and full vm/publish payload (assertionUri/authorAddress/ merkleRoot/kas/blockNumber) - create now reports `alreadyExists` (idempotent get-or-create); GET accepts author-scoped `agentAddress`; strict boolean validation of alsoShareSwm/ alsoPublishVm; published-activity attributed to the seal author - NEW route: GET /api/knowledge-assets/:name/wm/quads (replaces the legacy POST /api/assertion/:name/query quad dump) Full migration blueprint (6 missing routes, 30-helper relocation, 35 parity gaps, ~80 consumers) captured in docs/migrations/assertion-to-knowledge-assets.md. Typecheck-clean (cli tsc). Route unit-test mocks (listContextGraphs writability) and the remaining 5 missing routes follow in subsequent Stage-1 increments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o shared-assertion-helpers.ts Moves 30+ helpers (import-artifact resolution, owner-guard, semantic-enrichment RDF builders, promote-async job views, quad sort) out of assertion.ts into a new shared module so the new /api/knowledge-assets route ports can reuse them and assertion.ts can be deleted in Stage 4. assertion.ts re-imports them; behavior unchanged. Typecheck-clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nt onto KA surface
Adds POST /api/knowledge-assets/import-artifact/{resolve,read-markdown} and
POST /api/knowledge-assets/semantic-enrichment/write as faithful ports of the
legacy /api/assertion/* equivalents (new module knowledge-assets-import.ts,
reusing shared-assertion-helpers). Identical owner-guard (#872 relax on
public+open CGs for reads, strict for enrichment), 400/403/404/409/413 mapping,
response shapes, and the semantic_enrichment_written SSE. Collection-level paths
dispatched before :name parsing. Typecheck-clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n-status onto KA surface
POST /api/knowledge-assets/:name/wm/import-file (multipart) and
GET /api/knowledge-assets/:name/wm/extraction-status — verbatim ports of the
legacy /api/assertion/:name/{import-file,extraction-status} handlers (same
multipart parsing, MIME inference, per-assertion lock, extractionStatus
lifecycle, two-graph atomic insert, SSE side-effects, 400/409/413/415 mapping).
import-file dispatch is placed BEFORE the JSON safeParseJson preflight so the
multipart stream is read raw. Typecheck-clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ync + share-jobs Ports all 5 legacy /api/assertion/promote-async* routes onto the KA surface: POST /api/knowledge-assets/:name/swm/share-async (enqueue) GET /api/knowledge-assets/swm/share-jobs (list, ?state/limit/contextGraphId) GET /api/knowledge-assets/swm/share-jobs/:jobId (status) DELETE /api/knowledge-assets/swm/share-jobs/:jobId (cancel) POST /api/knowledge-assets/swm/share-jobs/:jobId/recover (recover) New module knowledge-assets-async-share.ts; reuses shared promote-job helpers. Collection job routes dispatched early (incl. new DELETE handling); enqueue inlined in the SWM section. Faithful 503/400/404/409 mapping + PromoteJobView shapes. Typecheck-clean. This completes the 6 missing KA route families — the /api/knowledge-assets surface is now a full superset of /api/assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…w route families) Updates the route-test harness for the new parity (mock listContextGraphs writability, ctx side-effect spies, full seal/publish payloads, alreadyExists) and adds coverage for every new route: wm/quads, import-artifact resolve/ read-markdown, semantic-enrichment/write, wm/import-file (multipart), wm/ extraction-status, swm/share-async, and swm/share-jobs list/status/cancel/ recover (incl. 503 worker-unavailable, agentAddress scoping). 61 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dge-assets Repoints every /api/assertion/* caller across the codebase to the unified KA surface (path mapping per docs/migrations/assertion-to-knowledge-assets.md): - cli api-client.ts (central SDK): all 7 assertion methods → KA; queryAssertion changed from POST-body to GET /wm/quads with querystring; promote→swm/share - mcp-dkg client.ts + adapter-openclaw dkg-client.ts: all assertion methods → KA (query POST→GET, history→GET :name, createAssertion reads alreadyExists) - adapter-hermes Python client.py: all 9 endpoints → KA (py_compile clean) - node-ui (src/api.ts, ui/lib/ontologyInstall.ts), network-sim (client + the server-side sim mock), agent/core doc-strings - ~14 devnet/test scripts (curl/fetch) repointed; living docs + SKILL.md updated Historical records (CHANGELOG, RFCs, ADRs, bug reports, async-promote specs) and unit/e2e test files left for later stages. Every touched TS package typechecks clean; Python py_compile clean; shell bash -n / node --check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…client/devnet tests - cli daemon route-level tests (promote-async-routes, promote-async-daemon- lifecycle, promote-route-not-persisted, async-promote-queue-e2e, import- artifact-routes, memory-graph-events, context-graph-write-path-validation) migrated from handleAssertionRoutes → handleKnowledgeAssetsRoutes at the KA paths, preserving every edge case (409 not-persisted, owner-guard 403, 503 worker-unavailable, validation 400s, SSE operations). Ran green via chain-free configs (31+2+4+5+32+23+28). - adapter-openclaw/hermes + node-ui + agent client tests repointed to KA URLs/ methods/shapes (openclaw 82+191+58 green); devnet integration tests + bootstrap repointed for the final live run. Also fixes a discard parity gap the migration surfaced: the KA wm/discard handler now evicts the cached extraction-status record (extractionStatus.delete via contextGraphAssertionUri) exactly as the legacy discard did, so a re-import after discard doesn't see a stale "completed". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/kc reads Removes packages/cli/src/daemon/routes/assertion.ts (3909 lines) and its dispatch in handle-request.ts. The /api/assertion/* HTTP surface is gone — everything now flows through /api/knowledge-assets/*. Two read-only chain-metadata routes that happened to live in assertion.ts but are NOT part of the assertion lifecycle (GET /api/kc/:id, GET /api/kc/:id/author) are extracted verbatim into the new routes/kc-chain-metadata.ts module and dispatched separately, preserving their exact paths/shapes. kc-author-route.e2e.test.ts repointed to the new module. BREAKING: /api/assertion/* routes no longer exist. All first-party clients, adapters, scripts, docs, and tests were migrated to /api/knowledge-assets/* in Stages 2-3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rix + rc.17 (#996) * fix(storage): blank-node-safe delete on SPARQL backends (sparql-http, blazegraph) `SparqlHttpStore.delete(quads)` (and the identical `BlazegraphStore.delete`) serialized every quad into a single `DELETE DATA { … }` block. SPARQL forbids blank nodes in DELETE DATA, so any quad whose subject/object is a blank node made a spec-compliant endpoint (Oxigraph server, Fuseki, Blazegraph) reject the whole statement with HTTP 400 ("Variables and blank nodes are not allowed in DELETE DATA"). This broke "Promote All → Shared" (WM→SWM, assertionPromote → store.delete of the CONSTRUCT'd source quads) for any entity containing nested/ anonymous RDF — the common case — and exposed every other store.delete(quads) caller (tentative/finalization cleanup, etc.) on the oxigraph-server backend. Only the SPARQL-over-HTTP adapters are affected. The embedded OxigraphStore and oxigraph-worker delete via the native object API (blank-node-safe), which is why every promote/share unit test (all on OxigraphStore) and the devnet (oxigraph- worker default) stayed green — while a fresh `dkg init`, which defaults to the oxigraph-server backend, hit it on real data. Fix: new `buildBlankNodeSafeDelete()` keeps ground quads on the fast DELETE DATA path and removes blank-node quads via `DELETE { … } WHERE { … }`, grouping connected blank-node components (linked by shared labels) into separate statements and rewriting each blank node to a query variable — the only spec-legal way to target existing blank-node structure over SPARQL. Per-component emission avoids cross-product WHERE joins that would silently delete nothing. Both adapters now share the builder. Tests (packages/storage/test/sparql-http-blank-nodes.test.ts, 20 cases): a broad matrix of blank-node shapes (object/subject bnodes, nested entities, RDF lists, shared bnodes, disjoint components, deep chains, mixed ground+bnode, cross-graph, the CONSTRUCT→delete promote reproduction) executed against a REAL embedded Oxigraph engine (same engine oxigraph-server runs) plus the full SparqlHttpStore HTTP path. A control test asserts the legacy DELETE-DATA-with-bnode form still throws on that engine. Verified: 20/20 pass on the fix; 16/20 fail on the legacy behaviour (the suite genuinely gates the regression). Root-cause of the test gap: the prior sparql-http test used a mock HTTP server that recorded the update string but never parsed/executed it, so invalid SPARQL passed. The new suite runs generated SPARQL through a real engine. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore: bump version to 10.0.0-rc.17 rc.17 ships the blank-node-safe SPARQL delete fix (see previous commit). No contract changes — the on-chain contracts stay at 10.0.3 and need no redeploy. Root + all 18 workspace packages 10.0.0-rc.16 → 10.0.0-rc.17. Internal deps are workspace:* and pnpm-lock.yaml does not pin workspace versions, so frozen-install CI stays green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(storage): run the conformance matrix against the sparql-http backend by default + fix deleteByPattern Adds the SPARQL-over-HTTP backend (the oxigraph-server default a fresh `dkg init` gets) to the shared TripleStore conformance matrix, backed by an in-process endpoint running the real Oxigraph engine — so it executes the adapter's generated SPARQL (no server binary, runs on every CI run). Also adds blank-node cases (object/subject bnodes, nested-entity-with-bnode-children = the WM→SWM promote shape) to the shared suite so EVERY backend is exercised with blank nodes. This is the matrix entry that would have caught the rc.16 blank-node DELETE-DATA bug. Wiring sparql-http into the matrix immediately surfaced a SECOND latent bug in the same class: `deleteByPattern`'s no-graph branch emitted `DELETE { ?g_ctx { … } }` (missing the GRAPH keyword) — invalid SPARQL, HTTP 400 on any real endpoint, never executed by a test before. Fixed in both `sparql-http.ts` and `blazegraph.ts`. devnet.sh already runs a backend matrix (nodes 1-2 oxigraph-worker, 3-4 blazegraph, 5-6 sparql-http/oxigraph-server) but SILENTLY fell back to the embedded store when Docker was unavailable — which is how no devnet node ever ran sparql-http during rc.16 validation. Added a loud coverage summary and an opt-in `DEVNET_REQUIRE_ALL_BACKENDS=1` strict gate so CI cannot silently shrink the matrix. Storage suite: 181 passed / 3 skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(pr996): address CodeQL + e2e review feedback - storage test: stop reflecting the caught exception text in the mock SPARQL server's HTTP response (CodeQL: exception-text-as-HTML + stack-trace info exposure). The adapter only inspects the 400 status; return a constant text/plain body and log the real reason to stderr. - node-ui: add the `widget-promote-all-btn` / `widget-publish-vm-btn` / `layer-action-result` data-testids the WM→SWM UI-cycle spec targets. They were missing, so the regression spec's first toBeVisible() would time out and the promote flow never ran. Tag only the LayerActionsWidget surface (the one the default WM/SWM `items` tab actually renders) to avoid .or() strict-mode multi-matches. - e2e: correct the stale SWM→VM `fixme` rationale — the bulk publish control now mints one KA per SWM assertion (Design B), so on the shared seeded CG (~25 roots) it fires ~25 on-chain mints; single-root publish (PublishPanel) is not wired into ProjectView nav. Kept deferred (on-chain mint already covered by the API sibling spec) with accurate reasoning. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(pr996): silence CodeQL in the new sparql endpoint helper The in-process oxigraph SPARQL endpoint added in 25124c2 reintroduced the same pattern just fixed in sparql-http-blank-nodes.test.ts: its catch block reflected the caught exception text into the HTTP response, which CodeQL flags as exception-text-as-HTML (#3984) + stack-trace info exposure (#3985). The SparqlHttpStore adapter only inspects the 400 status, never the body, so return a constant text/plain message and log the real reason to stderr. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(e2e): sort WM assertion graphs by name segment, not full URI (PR review) findWmAssertion sorted candidate graph URIs by the full string. URIs are .../assertion/<agent>/<name>, so a raw sort orders by the <agent> segment first and could pick an older assertion from a different agent on a shared devnet, making the rest of the spec read counts/markers from the wrong graph. Sort by the trailing assertion-name segment (the stable per-import key) instead. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(e2e): poll for CG readiness + isolate adapter-path store per test (PR review) - wm-swm-vm-ui-cycle.devnet: /api/status answering doesn't mean the seeded primary CG has finished registering, so listContextGraphs() can briefly return [] on a cold boot and the spec falsely skips. Poll a bounded window before the skip-check; a genuinely-empty operator devnet still skips. - sparql-http-blank-nodes (adapter-path describe): the long-lived endpoint served a single shared embedded store across both cases, so leftover data / an early failure could leak into the next case. Reset to a fresh store in beforeEach for isolation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(e2e): drive the WM→SWM→VM UI spec on the production-default oxigraph-server backend (no Docker) (#1014) * test(e2e): run devnet UI node on the production-default oxigraph-server backend (no Docker) The devnet's node 1 (the node the UI/e2e suite drives) ran `oxigraph-worker`, while fresh installs default to `oxigraph-server`. So the UI e2e never exercised the real backend — and SPARQL-over-HTTP-only bugs like #996 (blank-node DELETE DATA) could not reproduce in the UI flow. oxigraph-server was only available via Docker (nodes 5-6), which silently degraded when Docker was absent — the exact rc.16 trap. Switch devnet nodes 1-2 to the DAEMON-MANAGED `oxigraph-server` backend: the daemon auto-downloads + SHA-256-verifies + caches the Oxigraph binary and spawns it on its own port — NO Docker, cross-platform, production parity (it's what HariSeldon runs). Keep the Dockerized external Oxigraph on 5-6 as optional extra coverage; update the backend-matrix summary + DEVNET_REQUIRE_ALL_BACKENDS gate accordingly. Add a HARD precondition to wm-swm-vm-ui-cycle.devnet.spec.ts: assert node 1's /api/status storeBackend === 'oxigraph-server' and FAIL (not skip) otherwise, so the bug-reproducing backend can never silently regress to a trivial pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(devnet): pin per-node oxigraph-server ports (7900+n) to avoid the 7878 collision The daemon-managed oxigraph-server defaults to port 7878; two nodes on one host (devnet nodes 1-2) collided with 'Address already in use'. Give each its own port via store.options.port. Caught by booting the e2e devnet on the new oxigraph-server backend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(e2e): make the WM→SWM→VM UI spec green — stale-state wipe, import-result hooks, publish-policy retry Three issues surfaced by running the flagship oxigraph-server UI spec end-to-end: 1. devnet.sh: each `start` redeploys a FRESH chain but kept persisted per-node store state, so a node's stale `dkg:contextGraphOnChainId` triple made registerContextGraph short-circuit ("already registered") and never create the CG on the new chain → isContextGraphActive=false, publishPolicy reads 0, and every VM publish hit LU-5 "publish access-policy is unknown". Now wipe per-node state on a fresh-chain boot (keeping the cached Oxigraph binary), so nodes re-register against the new chain. Fixes local repeated-boot flakiness (CI was masked by always starting from a clean .devnet). 2. ImportFilesModal: the result element exposed `className="v10-import-result"` but none of the `data-testid="import-result"` / `data-import-status` / `data-import-triples` hooks the spec asserts on. Added them (the spec was written test-first; the component just never exposed the contract). 3. devnet-publish.ts: retry the SPECIFIC LU-5 "policy-not-confirmed" transient in publishToVm (≤45s) — defensive hardening for the genuine few-blocks registration→confirmation lag (distinct from #1 above), centralized so the global-setup seed and every publish caller benefit. Result: wm-swm-vm-ui-cycle.devnet.spec → import + promote (the #996 blank-node WM→SWM migration) pass through the REAL UI on the production-default oxigraph-server backend; VM-publish stays an intentional test.fixme (covered by the API-driven sibling spec). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Branimir Rakic <aleatoric@Branimirs-MacBook-Pro.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * fix(e2e): query the _meta marker with a hardcoded GRAPH IRI (PR review) readMemoryLayerMarker queried the meta partition with `GRAPH ?mg` + a CONTAINS(/_meta) filter. Under the daemon's scoped-query guard (documented in this file's header) the `<cg>/_meta` partition is ONLY reachable via a hardcoded `GRAPH <iri>`; the variable+filter form is for the data / _shared_memory partitions and 400s on a strict daemon, so the marker read would fail even after a successful promote. Build the exact meta-graph IRI (`did:dkg:context-graph:<cg>/_meta`, identical to what the agent's gossip/finalization handlers use) and query it directly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(e2e): scope dropzone 'supported-format hint' assertions to the modal import-files + import-data-display asserted page-wide getByText(/\.md/) for the dropzone's static supported-formats hint. On the shared devnet that also matches any .md document already imported (e.g. the WM→SWM→VM lifecycle spec's single-entity-fixture.md), tripping strict-mode with multiple matches. The hint lives in the import modal, so scope the assertions to importFilesModal.overlay. Hardens them against any shared .md content (root cause; green on main only because nothing else imported a .md there). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Branimir Rakic <aleatoric@Branimirs-MacBook-Pro.local> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…n routes
The Stage-2 consumer sweep missed 4 live calls in the browser API client:
import-file, create (publishTriples), promote, and extraction-status. Repoint
them to the unified /api/knowledge-assets surface (wm/import-file, POST
/api/knowledge-assets one-shot, swm/share, wm/extraction-status). Bodies and
response shapes are unchanged — the KA create route accepts the same
{quads, finalize, promote} one-shot (see api-client.createAssertion).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update: follow-up fixes + combined-state devnet validationTwo commits added since the initial push:
Combined-state validation (throwaway staging branch = latest
Two notes for the eventual
|
|
Subsumed into rc17-vm-wip (the rc.17 integration branch → main via #1053) — this work shipped as part of integrated rc.17. Closing as superseded. |
What & why
The v10 daemon carried two parallel HTTP surfaces for the same engine: the legacy
/api/assertion/*routes and the newer/api/knowledge-assets/*(KA) routes — both calling the identicalagent.assertion.*engine. That dual-route ambiguity caused a string of subtle bugs across the open PRs (#971/#978/#988/#990/#991/#992): missing side-effects, dropped response fields, validation gaps,alreadyExists/author-scoping regressions, vm/publish status mismatches.This PR removes
/api/assertion/*entirely and makes/api/knowledge-assets/*the single, complete surface — one coherent WM→SWM→VM lifecycle. The KA file header always said the legacy routes would be redirected/retired in a follow-up; this is that follow-up, done as a clean cut.Targeting rc.17. Base
integration/v10-devnet(which has the KA hardening main lacks).Staged sequence (9 commits)
GET .../:name/wm/quads(wasPOST :name/query),POST .../:name/wm/import-file(multipart) +GET .../wm/extraction-status,POST .../import-artifact/{resolve,read-markdown},POST .../semantic-enrichment/write, and thepromote-asyncqueue asPOST .../:name/swm/share-async+GET/DELETE .../swm/share-jobs[/:jobId][/recover]:namedecode/validation, SSEemitMemoryGraphChanged+recordAssertionActivity+ notifications on every mutation, full finalize/publish payloads,alreadyExists, author-scopedagentAddress, strict boolean validation, seal-author activity attributionshared-assertion-helpers.ts; 61 route unit testsapi-client.ts),mcp-dkg,adapter-openclaw, the Hermes Python client,node-ui(UI + e2e),network-sim(incl. its server-side mock),agent/core, ~14 devnet scripts, and living docs/skills. The one shape change handled throughout:queryPOST-body → GET-querystring.handleAssertionRoutes→handleKnowledgeAssetsRoutespreserving every edge case (409 not-persisted, owner-guard 403, 503 worker-unavailable, validation 400s). Also fixes a discard parity gap the migration surfaced (KA discard now evicts the cached extraction-status, matching legacy).assertion.ts(−3909 lines) + its dispatch. The two read-only chain-metadata routes that happened to live there (GET /api/kc/:id,GET /api/kc/:id/author) — not part of the lifecycle — were preserved verbatim in a newkc-chain-metadata.tsmodule.Full path mapping + the 35 parity gaps are documented in
docs/migrations/assertion-to-knowledge-assets.md.Verification
/api/assertion/*→ 404 everywhere;/api/knowledge-assets/*create/write/wm/quads/swm/share-jobsall live with the new parity fieldsv10-core-flows4/5, including the decisive "create/write/finalize/promote emit memory_graph_changed in order" lifecycle test. The 1 failure is an unrelated Random-Sampling epoch-score timing flake, downstream of a successful KA publish./api/assertion/*no longer exists. All first-party clients/adapters/scripts/docs/tests are migrated in this PR. External callers must move to/api/knowledge-assets/*(mapping in the migration doc).🤖 Generated with Claude Code