Commit Graph
4729 Commits
Author SHA1 Message Date
jason.woltje 0244917e64 fix(console): map Task 4's mode refusal and read its draft shape in the request routes (#1839)
The bus refuses a stale mode revision as request-mode-revision-conflict, so the Console route maps that
code to 409 instead of request-mode-stale and request-mode-unchanged, which no bus emits. The mode copy and the
request-mode-changed wait label describe Task 4's project-level change. The draft route relays Task 4's
{ candidate, prd, requirements } reply when its candidate names the request, and maps prd-candidate-required to 409.
2026-11-04 11:21:43 -06:00
jason.woltje 4e79eaace1 fix(console): check the owner-to-PM edge on every intake control, refuse request URLs without SSO, bound the browser waits (#1839)
Review r1 of lifecycle Task 3.

C1: the four owner controls (answer, attach, mode, cancel) inserted and routed a
human-to-PM message without the messaging policy check message.send makes. The
shared writer now reads the current human-to-PM edge inside the control's
transaction, before it inserts or routes anything, and refuses with
message-sender-refused. The refusal rolls back the journal row, the human.input
event, the message and a withdrawn selection decision. The cancel withdrawal
checks the same edge. Regression tests close the edge after the question and
cover all four controls, then reopen it and prove the same call lands once.

I1: serve.mjs read sso.publicOrigin for a request URL when the Console ran
without --sso (or in preview mode), which ended the process on an unauthenticated
GET /api/requests/r1. A request route now answers through requestHTTP with no
transports and no SSO fields: unavailable on a read, read-only on a write, 405
for the wrong method. Listener tests cover GET and POST without SSO and in
preview mode, named and loopback, and the process keeps serving.

M1: the browser test waiter allowed about 3 s and reload() could run out its loop
without failing. Waits are now deadline-bound at 15 s and name the expression,
and reload() waits for the new document's own ready flag and the mounted chat,
and throws if it never gets there. The no-repost and stored-answer assertions of
the lost-answer case are unchanged.

Migration 3p-3q is unchanged.
2026-11-04 11:21:43 -06:00
jason.woltje eb551b1f89 feat(console): choose a request mode, answer questions and confirm attachment with buttons (#1839)
The owner handles a new request from the Console: pick the mode (new
autonomous, new guided or an existing project), answer the project
manager's questions and confirm an attachment, with no copy-to-terminal
step.

Bus: 3p->3q append-only migration widens the request_events kinds. The PM
asks one question with request.question. The owner-only
request.answer.console, request.attach.console, request.mode.console and
request.cancel.console join the two console reads in CONSOLE_VERBS. An
answer is an attributed human.input linked to its question and request,
recorded once per op and never a PRD approval. An attach pick makes the PM
propose that project as a confirmation decision; nothing is mapped on the
pick. A mode change takes the expected revisions and leaves the bound plan
as evidence. Cancel keeps a created project.

Console: request-http.mjs serves GET /api/requests/:request, /projects and
/draft and POST /answer, /attach, /mode and /cancel with the signed-in
owner, same-origin and bounded-body checks of the decision routes.
requests.js draws the panel under the owner's chat message, answers the
mode decision through the existing confirmation endpoints, and after a lost
answer response reads the request first and resends the same op only on the
owner's click.
2026-11-04 11:21:43 -06:00
jason.woltje 793f3b157d fix(cli): upgrade and roll back Case 2 proxy hubs (#772) 2026-11-04 11:07:57 -06:00
jason.woltje c098fa022c feat(bus): approve and activate an exact product PRD version (#1839) 2026-11-04 10:49:15 -06:00
jason.woltje ea0e885bc9 fix(bus): keep foreign working repos refused, hold the organization lock through protected commits, fence serving on the activate receipt (#802) 2026-11-04 10:38:54 -06:00
jason.woltje 7f5bbbf747 fix(bus): confine the working repo, serialize freshness with protected writes, fence readiness on publication (#802) 2026-11-04 10:38:54 -06:00
jason.woltje 1031686140 feat(tasks): recover an uncertain tracker create without a second post (#1839) 2026-11-04 10:33:10 -06:00
jason.woltje 6f41eddb20 fix(harness): keep tool input schemas free of top-level oneOf (#1839) 2026-11-03 21:55:20 -06:00
jason.woltje 1de685ccad feat(harness): give the PM typed intake tools for pre-task requests (#1839) 2026-11-03 21:53:15 -06:00
jason.woltje 1648af7d4e feat(seat): read one reviewed artifact blob with bounded safe Git reads (#1839) 2026-11-03 21:46:53 -06:00
jason.woltje 6dd4f92b0a docs: record lifecycle Task 2 review, fix round and landing decision (#797) 2026-11-03 20:51:19 -06:00
jason.woltje 8830030412 fix(bus): confine bootstrap writes, keep unproven partials, recheck freshness per effect, wire host preparation (#1839) 2026-11-03 20:41:46 -06:00
jason.woltje c558498062 feat(bus): bootstrap a confirmed new project and admit it at runtime (#1839)
Lifecycle Task 2 (#797). A confirmed new-project selection now becomes a
usable product: the code checkout, the organization mapping, the records
repository with the original intent as the owner's spec commit, the chat
association and the working repository, each journaled as its own receipt
(bootstrap:<plan_digest>:<step>) and recoverable after a kill.

- business: addProject is a private exact-add writer under the existing
  organization lock and receipt discipline; hold runs a synchronous check
  under that lock; findAddition recovers a prepared receipt. Ordinary
  writers still refuse every project change.
- bus: prepareBootstrap and executeBootstrap; activateManagedProject
  reloads only a receipt-backed additive root and admits it to the broker
  after readiness; request.admission host IPC op for the launcher.
- #800: the bound organization revision and association are re-verified
  inside the organization lock at the start of execution; a mismatch
  records request-plan-stale and performs no effect.
- cli: trackerFor skips reserved products; the launcher refuses to clone a
  product the bus has not admitted.
- seat: initializeWorking seeds the working repository, host-only, with no
  work ref or push authority.
- schema: append-only migration 3o to 3p widens the request_events kind
  list with bootstrap.stale and bootstrap.completed.
2026-11-03 19:50:32 -06:00
jason.woltje 138171ca0d docs: record lifecycle Task 1 review, fix round and landing decision (#797) 2026-11-03 18:21:15 -06:00
jason.woltje bec3583286 fix(bus): keep the request route across setup waits and recheck association after admission (#1839) 2026-11-03 18:15:48 -06:00
jason.woltje 3b071cac7b feat(bus): persist pre-task request intake and selection (#1839) 2026-11-03 17:53:45 -06:00
jason.woltje 1e1357cb6e feat(console): answer every eligible decision option (#1839) 2026-11-03 17:25:24 -06:00
jason.woltje 831d15f0b4 docs: prepare lifecycle execution and original request evidence (#1839) 2026-11-03 17:01:26 -06:00
jason.woltje 63bd48a5f8 docs: specify working repository bootstrap interface (#797) 2026-11-03 16:59:51 -06:00
jason.woltje 260f2ce7ed docs: plan request lifecycle implementation and PRD addendum (#797) 2026-11-03 16:59:51 -06:00
jason.woltje a34f79692e docs: record user terminology migration inventory (#1840) 2026-11-03 16:52:09 -06:00
jason.woltje 930d77eeca docs: track user terminology and identifier migration (#1840) 2026-11-03 16:46:04 -06:00
jason.woltje 7e849a7b0b docs: plan GPT implementation of Console decision answers (#1839) 2026-11-03 16:37:00 -06:00
jason.woltje ec5354bcc2 docs: propose request-to-product intake and decision flow (#1839) 2026-11-03 16:32:20 -06:00
jason.woltje 7024e35f59 Merge #784 R2: the paste test catches a handler that enters the live page (Vikunja #784) 2026-11-02 22:35:21 -06:00
jason.woltje cbc43c6edc docs(editor #784 R2): BUILD-LOG and SESSIONS lines (Vikunja #784) 2026-11-02 22:34:45 -06:00
jason.woltje e71222d1a0 test(webui): the paste test catches a handler that enters the live page (Vikunja #784 R2)
Chromium also targets the document when a handler-bearing element is attached to the live page and removed before the CSP event is sent, so the readHTML filter alone (directive, source file, line, target document) could not tell that from the inert parse. The page now also records, with a MutationObserver on the live document, every element that enters the page carrying an on* attribute and every on* attribute set on an element in the page, and h.quiet() asserts the record is empty. The live-handler test gains a case for a handler attached and removed in the same turn, and checks per case that the observer saw the handler enter the page (not for the element never attached). Test-only; no product change.
2026-11-02 22:31:45 -06:00
jason.woltje 18a0bd47d7 Merge Console writes: factory Console takes the paired --sso --send; .53 launches through the proxy (Vikunja #415) 2026-11-02 22:16:25 -06:00
jason.woltje a702076576 docs: record Vikunja #415 and #416 Console writes build (BUILD-LOG, SESSIONS) 2026-11-02 22:12:57 -06:00
jason.woltje 4d1d5ee8ee Merge #784: set aside the editor paste's inert-parse CSP reports under Chromium 156 (Vikunja #784) 2026-11-02 22:09:19 -06:00
jason.woltje bd79daffdd fix(case2-unit): factory Console takes the paired --sso --send; .53 launches through the proxy (Vikunja #415, #416)
The factory profile of scripts/case2-unit.sh now takes the bare pair
--sso --send, ported from the lane's serve_flags rule (b00019548). Either
flag alone refuses, and the proxy preview set and the proof pair still
refuse. Before any unit write, a bounded query run as the stack user,
through the existing config-query supervisor, checks that the stack
user's system config has console.sso; the Console refuses --sso without
it at start (packages/webui/src/cli.mjs:95). The server needs no change.

The library profile is unchanged: still read-only, still refusing --send
and --sso. Taking library --send with forward-auth and proof would need
the #385 refusal reverted in cli.mjs:63, serve.mjs:681 and the service,
release and upgrade scripts, so it is not done here.

docs/guides/case1-business.mosaic-stack.json now launches
claude-opus-5-5 with provider proxy, route anthropic, under account
factory with credential proxy.
2026-11-02 22:09:01 -06:00
jason.woltje 1bf83078b4 docs(editor Chromium 156): BUILD-LOG and SESSIONS lines (Vikunja #784) 2026-11-02 22:07:43 -06:00
jason.woltje 0c6aa1a1e2 test(webui): set aside the paste's inert-parse CSP reports under Chromium 156 (Vikunja #784)
Chromium 156 (host upgrade 2026-11-03) checks script-src-attr when an
inline handler attribute is created, in a document with no window as well
(WPT PR 62762; the follow-up PR 64290 that skips windowless documents is
not in 156). prosemirror-view's readHTML parses pasted HTML into the body
of a new createHTMLDocument, so the paste test at :154 got one report per
pasted handler (img onerror, a onclick, a onmouseover, svg onload), each
at editor.js:8212 with the document as target. No pasted handler reaches
the live document; Chromium 153, 151 and Brave 154 report nothing there.

The page record now sets aside only a script-src-attr report whose event
names editor.js at that readHTML line and whose target is the document.
The line is found from the bundle's text, and the lines that make the
parse target a new createHTMLDocument body are pinned; the test file
refuses to load if they change. h.quiet(), inert(), injected, errors and
rejections are unchanged. No product or CSP change.

New test: after a hostile paste, three live handlers each still fail
h.quiet() with one script-src-attr report: an onclick set on an editor
paragraph and clicked, an img onerror parsed into a page-owned detached
element, and an onclick prosemirror-view sets through its attributes prop
(a report naming editor.js at another line).
2026-11-02 22:03:26 -06:00
jason.woltje b9811c492f Merge #774 R2: the first proxy check gates the route the final check picks (Vikunja #774) 2026-11-02 21:52:46 -06:00
jason.woltje d27fe0ea95 docs(row 332 R2): BUILD-LOG and SESSIONS lines for the route prediction fix (Vikunja #774) 2026-11-02 21:41:33 -06:00
jason.woltje b8f01a189f fix(row 332 R2): proxy first check gates the route the final check picks (Vikunja #774)
Route selection moves into one helper, pickRoute(proxyOf), that both checks call: the
primary preflight, and the fallback with its own preflight only when the primary's
fails, with the same messages. The final check passes proxyFor and is otherwise
unchanged. The first check of a proxy launch passes a stand-in session proxy (marker
key PENDING_ADMISSION_KEY and the configured loopback base URL), which reaches only
the preflights, then runs gate() with a null session proxy on the predicted route only.
The round 1 union over both routes is gone.

The managed fixture takes extra (more harnesses switched on), real (harnesses loaded
unfaked) and hostEnv. Seven tests in proxy-launch-order.test.mjs.
2026-11-02 21:37:48 -06:00
jason.woltje e6ba6eb8dc Merge row 333 follow-ups: orphan ids after a failed lookup, dropped proxy usage batches, business-imports scanner, held refusal-test ports (Vikunja #783, #778, #779, #782) 2026-11-02 21:22:41 -06:00
jason.woltje 3c7a7740c4 docs(row 333 follow-ups R3): BUILD-LOG and SESSIONS lines (Vikunja #779) 2026-11-02 21:16:02 -06:00
jason.woltje 180fb8ab00 fix(row 333 follow-ups R3): a spread before import is a dynamic import (Vikunja #779)
The negative lookbehind in DYNAMIC and in the dynamic branch of IMPORT excluded any dot
before `import`. That also skipped the last dot of the spread punctuator, so
`${({...import('../x.mjs')})}` returned [] for an existing, a missing and an outside
target, and `{...import(name)}` in plain code was neither returned nor refused.

Both patterns now share one fragment: a word character or $ before `import`, or a single
dot (x.import, x?.import), makes it a property call; a dot that follows another dot ends a
spread and does not exclude it. `1..import(` is read as an import too, which errs toward
refusal; the header names it.

Tests: a spread inside a substitution refuses for an existing, a missing and an outside
target and for array spread; a spread in plain code is returned, reaches the target
checks, and is refused when its argument is not a plain string; x.import and x?.import
stay property calls in plain code and in a substitution.
2026-11-02 21:13:56 -06:00
jason.woltje 42c72d781b docs(row 333 follow-ups R2): BUILD-LOG and SESSIONS lines (Vikunja #779) 2026-11-02 21:00:22 -06:00
jason.woltje d3b6ab11da test(row 333 follow-ups R2): refuse a dynamic import inside a template substitution, read division after ++ and -- (Vikunja #779)
The business-imports helper blanked a whole template literal, so a dynamic import inside a
${...} substitution was neither found nor refused and skipped the existence and containment
checks. The scanner now reads a substitution to its closing brace, refuses it with
"unsupported import inside a template substitution" when it holds a dynamic import, and drops
it otherwise, so a nested template and a brace inside a string no longer end it early.

A slash after a postfix ++ or -- was read as the start of a regular expression, so
`n++ / 2` threw "unterminated regular expression". After a ++ or -- pair that follows an
operand the slash is division; after one that follows an operator it still starts a regular
expression. The header names the rare form this still misreads.

Output on the real tree is byte-identical.
2026-11-02 20:59:35 -06:00
jason.woltje a605e44d1e docs(row 332): BUILD-LOG and SESSIONS lines for the proxy first check fix (Vikunja #774) 2026-11-02 20:52:59 -06:00
jason.woltje 85ecdfbde5 fix(row 332): proxy first check runs every key-independent refusal; testProxy test option (Vikunja #774)
F1: with system.proxy set, the first launcher check no longer returns
before the account, sandbox, capacity and limit checks. It skips only the
harness preflight and the session proxy key match, which need the key P2
admits in startLocked. A launch that can never start now refuses before
authorizeLaunch, the workspace and checkouts, and modelPreflight. The first
proxy pass accepts only credential "proxy" for a declared account, tries
the fallback model when the primary refuses, and lets mayLaunch
key-quota-no-model-key through so P2 can issue the first key. The final
check is unchanged and still runs before newRun and spawn.

F4: the proxy test option is renamed testProxy and createLauncher throws
test-proxy-refused when it is given with system.proxy.
launch-account.test.mjs is a second caller, so the option stays.

Tests: proxy-launch-order.test.mjs (6), launcher.test.mjs F4 throw test;
the managed launch fixture gains bwrap, admit and trace.ops.
2026-11-02 20:51:51 -06:00
jason.woltje 2ba22b4cbf docs(row 333 follow-ups): BUILD-LOG and SESSIONS lines (#1831) 2026-11-02 20:41:10 -06:00
jason.woltje 5fbf3d2b51 test(row 333): hold the port a refusal test uses instead of probing and closing it (Vikunja #782)
control-board serve.test: the non-loopback refusal test took a port from a
probe-and-close helper and then called startServer on it. It now binds its
own listener, keeps it for the whole test, and closes it in finally, so the
port cannot be taken by another process in between.

stack-release.test T-I4: the free port goes only to webui-service.sh install
rows that refuse before any listen, so it is now a closed lease
(leaseClosedPort), which refuses connects and blocks other binds.

The 22 other freePort callers hand the number to `stack-release.sh rehearse`,
which probes it and starts a Console later. That needs a script change and
is left as a design, with a comment above freePort naming the window.
2026-11-02 20:22:14 -06:00
jason.woltje 0e7e203db8 test(row 333): harden the business-imports helper and test it on small trees (Vikunja #779)
The helper read imports with a regex that treated a commented import as live, missed
`import (` written with a space, skipped a missing target inside packages/business/src
before checking it existed, and resolved targets lexically. It now blanks comments
(and ignores import forms inside strings and regular expressions) before matching,
counts a spaced or line-broken dynamic import, throws on a dynamic import whose
argument is not a plain string and on a file with an unterminated string, comment,
template or regular expression, checks every target exists and is a regular file
before the inside-own-directory skip, and refuses a target whose real path is outside
the real repository root. The output for the real tree is unchanged.

business-imports.test.mjs builds small trees under a private temp directory.
2026-11-02 20:14:25 -06:00
jason.woltje 38cfa72335 feat(row 333): count dropped proxy usage batches and show them in the Console (Vikunja #778)
The usage queue read drains the proxy. A batch that is then not written (the
client or the bus refuses its shape, an ambiguous key owner, a failed write) is
lost, and the Console showed proxy totals with no sign of it. The reader now
counts these per enabled business since the bus started, the proxy view returns
the count as droppedBatches, and the Console proxy section says so above 0.
A read that failed before the proxy answered or that it refused drained nothing
and is not counted. Telemetry only; no quota reads it. The log line stays
proxy-usage-read-failed.
2026-11-02 20:08:50 -06:00
jason.woltje 3dc824ea12 fix(row 333): a failed lineage lookup forgets orphan ids the document dropped (Vikunja #783)
observeOrphans kept the whole remembered set when the lookup failed, so a leaf
removed and added back during the failure logged no second orphan line. It now
keeps only the ids the refreshed document still holds and adds none. Quota
decisions are unchanged: a failed lookup still refuses.
2026-11-02 20:05:28 -06:00
jason.woltje 1ff45c4ad6 Merge row 333: proxy usage and human model-key quotas, R4 (Gitea #1831) 2026-11-02 19:40:46 -06:00