Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,11 @@ exist on the post.

## CI gates

Record hosted results with their exact commit and run URL, including skipped
tests and warnings. Counts from independent PRs are not evidence that their
changes were integrated. Preserve local failures alongside later hosted success;
an evidence-only commit still needs its own current-head required checks.

`.github/workflows/tests.yml` runs the full suite on every PR to `main`.
Do not weaken, skip, or `continue-on-error` a failing check -- fix the
underlying cause or, for a genuine false positive in a third-party scanner,
Expand Down Expand Up @@ -422,3 +427,27 @@ columns). Do not silently rewrite either historical form. The SHACL
shapes graph (`docs/ontology/lineageweave-kg-shapes.ttl`) is the
closed-world data-validation boundary for DB-to-RDF projections and is
published beside the ontology.

## Authorization-sensitive asynchronous UI

Bind imperative result, error, and loading completion to the originating
component authorization lifecycle. Token equality alone cannot distinguish
retired A from current A after A → B → A. Clear prior questions, answers, and
evidence selection when credentials change, and verify that a retired success
or failure cannot end the current request's loading state. Keep behavioral
regressions synthetic; UI retirement does not itself cancel a server-side job.

Pass a native AbortSignal through client polling and fetch when retiring an
authenticated screen. Preserve the abort reason instead of reporting a network
failure, and clear delay timers/listeners. Result-admission guards remain necessary
for already-resolved work. Verify both transport cancellation and A → B → A
state admission; neither establishes server-job cancellation.

Do not infer Ask job failure from browser observation age. Queue wait and model
execution are distinct; follow durable terminal status or native cancellation.
Before removing a worker execution deadline, inspect orphan recovery: an
age-only requeue rule can duplicate a still-live computation without claim fencing.

Keep transport exceptions at the diagnostic boundary. Buyer-facing error copy
must not use String(error) or provider detail; reuse existing localized recovery
guidance and preserve explicitly supported status-specific behavior.
39 changes: 39 additions & 0 deletions docs/adr/0039-global-ask-agent-source-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,42 @@ explicit next action; the agent never fabricates an answer or citation.
response.
- The initial context is bounded to 50 recent rows. Retrieval/reranking is a
later upgrade if corpus size or prompt budget requires it.

## Proposed amendment: client observation lifetime (2026-09-07)

This amendment remains Proposed pending protected review. It does not change
the Accepted evidence-source decision above.

### Context and decision drivers

The client currently abandons a durable Ask job fifteen minutes after
submission, including queue wait. A synthetic deferred-status regression
reproduces abandonment after a queued job becomes running, before its next
succeeded response can be read. Elapsed observation time does not establish
provider failure. The requested model policy has no default application limit.

### Considered options and proposed outcome

Retaining or increasing the fixed ceiling bounds browser polling but still
rejects valid work solely because time elapsed. A second browser timeout setting
duplicates policy outside contextual-orchestrator. Instead, continue the existing
two-second polling until a terminal response, transport failure, or native
AbortSignal cancellation. Keep credential-generation admission checks for work
that already resolved when the screen was retired.

### Consequences and confirmation

The browser can observe late answers without resubmitting paid work. Polling can
continue indefinitely while a visible screen follows a stranded job; cancellation
on unmount or credential change still retires client I/O, not the server job.
The regression must advance past the former ceiling, observe a nonterminal
status, and then receive the actual completed answer. Existing cancellation
tests must continue passing without timer or listener leaks.

This is only the client observation decision. The backend's 600-second execution
deadline, answer socket limit, and 660-second age-based orphan recovery remain
an unresolved policy conflict. Removing execution limits requires a separate
worker-liveness and claim-fencing decision so recovery cannot duplicate a live
computation. Preserve ADR 0213's rule against holding pooled database connections
during provider work. No model administrator contract or end-to-end unlimited
execution is established by this amendment.
133 changes: 133 additions & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -932,3 +932,136 @@ The ONET rows stacked into base branches (#743/#745/#746/#740/#732) reached
`main` together through the #759 promotion; their per-base merge records are
historical evidence only. The job-architecture artifact ship originally via
#749 is now re-verified on `main` from the promotion.


## Ask authorization lifecycle repair (2026-09-07)

A pending Ask response could enter a later authorization lifecycle after
A → B → A. Deferred success displayed the retired answer; deferred HTTP 403
displayed the retired transport error. Both were assertion failures, not test
deadlines, in the initial two-case reproduction.

The component now clears question, cutoff, answer, error, external-verification
selection, loading, and evidence selection when its credential changes. It reuses
the existing component-generation pattern from Customer Master: request success,
failure, and loading completion require both the current credential and the
originating authorization generation. Cleanup retires the generation on token
change or unmount. No global session service, token persistence, API, or schema
is introduced. Server ABAC and ADR 0216's cutoff evidence remain authoritative.

The regression cases also start a new request after re-entry and require the
retired success/failure to leave that request loading until its own answer arrives.
This is UI admission evidence; it does not establish cancellation of a running
server job or the existing client poll loop. Those transport lifetimes remain a
separate gap, as does current-error copy that can expose transport details.

The repair is based on protected main `83eba56149eb802cd63642c507c324c9976ec78e`
and is independent of the unmerged translation-ledger foundation in PR #929/#932.
The initial experiment used the PR #932 worktree, then moved only the Ask diff to
an isolated main-based branch; the translation worktree was restored clean.
No real source records, new containers, or deployment were used.

Validation on the main-based candidate: 2/3 Ask tests passed; the deferred-success
case exceeded its unchanged five-second deadline. The expanded deferred-denial
case and existing cutoff/public-evidence case passed. The earlier narrower
two-case repair run passed 3/3 before adding the current-request loading check;
it is not substituted for the final run. Lint and TypeScript/production build
passed; the existing large-chunk warning remains. The change stays Draft pending
complete verification, independent review, and real-account acceptance.


### Ask client transport retirement follow-up (2026-09-07)

The UI guard alone did not retire transport: cancellation during a running-job
poll delay still caused a third HTTP request, and credential/unmount cleanup
supplied no AbortSignal. Three behavioral assertions reproduced those gaps.

The existing backendFetch RequestInit now carries a native AbortSignal from
AskAgentPanel through job submission and status reads. Component cleanup aborts
its request controller. The shared polling loop checks cancellation before
submission, before each status request, and after a returned status; delay abort
clears its timer, while normal delay completion removes the listener. The fetch
error boundary preserves the native cancellation reason. Existing generation
checks still reject retired results, errors, and loading completions.

This closes the client-poll-loop gap recorded above. It does not add a server
job cancellation endpoint or change the existing execution/deadline policy.
Current-error copy can still expose transport details and remains a separate
verified gap. No provider SDK, session store, or dependency was introduced.

Reference: MDN contributors. (2026, August 27). *AbortSignal: throwIfAborted()
method*. MDN Web Docs.
https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/throwIfAborted

Final focused validation passed 15/15 tests across the Ask and API files in
3.04 s, including the previously timed-out expanded re-entry success case,
pre-submission abort, in-flight fetch abort, and delay cancellation without another
request. Lint and TypeScript/production build passed with the existing chunk
warning. This supersedes the earlier focused timeout result, not the outstanding
full-suite, hosted, real-account, or page-latency acceptance requirements.


### Shared operation-error copy (2026-09-07)

Ask's current-error leak came from the shared orchestratorUnavailableMessage
fallback used by Chat, Keymen extraction, Evaluation, Commitment derivation,
and Ask. Non-503 errors were stringified into buyer copy. Three negative cases
(network failure, HTTP 403, HTTP 500) reproduced missing recovery guidance and
visible transport details. The fallback now reuses the existing translated
view-recovery sentence; the existing 503 saved-evidence guidance is unchanged.
No second catalog or dependency was introduced. This covers this formatter's
five callers, not every error path in the application.

The Ask/API run passed 17/18 cases, including all three new error-copy checks;
the existing cutoff/public-verification test exceeded its unchanged deadline.
A subsequent sibling-test invocation failed before test execution because the
Vitest forks worker did not respond. Neither is labeled a green suite.

Separately, hosted Tests run 34082387676 on ontology PR #959 commit
`96ce3de6190f1f66f140663f034427fd4d78d3a4` passed the frontend's 544 tests across
58 files and its lint/build, plus 1768 backend tests (147 skipped, one warning).
Skipped tests and repository-job success do not prove independent review,
all required central checks, private runtime acceptance, or deployment.

The correctly selected four existing 503 sibling tests passed in 6.86 s:
Chat, Evaluation, Keymen extraction, and Commitment derivation retained their
saved-evidence guidance. Lint and production build passed; the existing chunk
warning remains. The earlier worker-start failure ran no tests and is not
counted as a sibling assertion failure or success.

### Ask hosted integration evidence (2026-09-07)

[Tests run 34084171460](https://github.com/ContextualWisdomLab/LineageWeave/actions/runs/34084171460)
completed successfully on PR #972 code revision
`2242ef05cbd9cc61e836d87e1a838e7a8edd2589`. The frontend job passed 540 tests
across 58 files, lint, and production build. The backend job passed 1768 tests
with 147 skipped and one warning in 1251.00 s. This is the first full hosted
success recorded here for the combined Ask authorization, transport retirement,
and shared error-copy changes; the earlier local timeout evidence remains above.

The 540-test result belongs to this Ask revision; the 544-test result above
belongs to the independent ontology revision. Neither test count proves that
the other PR's changes were integrated. Skipped integration cases, independent
review, central required checks, product-owned authentication, eight-language
database resources, deployment, and authenticated all-page p95 remain separate
acceptance obligations. Subsequent documentation commits require their own
current-head checks; this run remains evidence for the cited code revision.

### Ask observation lifetime (2026-09-07; proposed ADR 0039 amendment)

A synthetic API regression moved the browser clock sixteen minutes forward
after a queued response. The next status was running, with a succeeded answer
available afterward. The client threw its fixed fifteen-minute timeout instead
of fetching that answer: 1 failed, 10 passed before repair. Removing the elapsed
observation ceiling from the shared askAgent poller preserved terminal responses
and native cancellation. The correctly selected API and Ask panel suites passed
19/19 in 11.65 s. An initial selection named a nonexistent panel file and ran
only the API suite (11/11); it is not counted as panel coverage.

The backend still enforces a 600-second execution deadline and a shorter answer
socket timeout. Recovery requeues running rows after 660 seconds because it
assumes the deadline ended any live computation. Removing that deadline alone
would invalidate the recovery assumption and could duplicate work. Worker
liveness, claim fencing, crash recovery, and the contextual-orchestrator model
administrator contract remain required before claiming the requested default-null
end-to-end model lifetime. No provider or private-runtime call was made here.
45 changes: 33 additions & 12 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ function orchestratorUnavailableMessage(err: unknown, action: string): string {
if (err instanceof BackendError && err.status === 503) {
return `${action} ${t("is temporarily unavailable.")} ${t("Saved evidence is still available.")}`;
}
return String(err);
return t("This view is unavailable. Refresh once; if it fails again, contact your administrator.");
}

function LanguageSwitcher({ accessToken }: { accessToken?: string }) {
Expand Down Expand Up @@ -5052,6 +5052,25 @@ export function AskAgentPanel({
const [asking, setAsking] = useState(false);
const [verifyExternal, setVerifyExternal] = useState(false);
const [evidenceLayerPostId, setEvidenceLayerPostId] = useState<string | null>(null);
const authGeneration = useRef(0);
const requestController = useRef<AbortController | null>(null);
const currentAccessTokenRef = useRef(accessToken);
currentAccessTokenRef.current = accessToken;
const [inputAccessToken, setInputAccessToken] = useState(accessToken);
if (inputAccessToken !== accessToken) {
setInputAccessToken(accessToken);
setQuestion("");
setKnowledgeCutoff("");
setAnswer(null);
setError(null);
setAsking(false);
setVerifyExternal(false);
setEvidenceLayerPostId(null);
}
useEffect(() => () => {
authGeneration.current += 1;
requestController.current?.abort();
}, [accessToken]);
const now = new Date();
const localKnowledgeCutoffMax = new Date(
now.getTime() - now.getTimezoneOffset() * 60_000,
Expand All @@ -5068,22 +5087,24 @@ export function AskAgentPanel({
setError(t("Enter a valid knowledge cutoff, then ask again."));
return;
}
const controller = new AbortController();
requestController.current = controller;
const requestAuthGeneration = authGeneration.current;
const requestAccessToken = accessToken;
setAsking(true);
setError(null);
try {
setAnswer(
await askAgent(
accessToken,
normalized,
verifyExternal,
cutoff,
),
);
const response = await askAgent(accessToken, normalized, verifyExternal, cutoff, controller.signal);
if (requestAccessToken === currentAccessTokenRef.current && requestAuthGeneration === authGeneration.current) {
setAnswer(response);
}
} catch (err) {
setAnswer(null);
setError(orchestratorUnavailableMessage(err, t("Ask Agent")));
if (requestAccessToken === currentAccessTokenRef.current && requestAuthGeneration === authGeneration.current) {
setAnswer(null);
setError(orchestratorUnavailableMessage(err, t("Ask Agent")));
}
} finally {
setAsking(false);
if (requestAccessToken === currentAccessTokenRef.current && requestAuthGeneration === authGeneration.current) setAsking(false);
}
}

Expand Down
Loading
Loading