You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
console(sharing): a password-protected share link sends its password in a header, not the request URL — and the landing page does not prompt for a password on a link that requires sign-in (the console half of objectstack-ai/objectstack#21839) #11649
Filed by the triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U. It is the console half of objectstack-ai/objectstack#21839, which names objectui as the owner of the console transport. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes and positions only: that card's detail is withheld (RUNNER rule 2).
The step (after the server accepts the password in a header)
Transport. The share-link landing page sends a link's password in a request header, never in the request URL.
Side issue (UX, from the same card). A link that requires sign-in shows the sign-in path, not a password prompt.
Done when
The password appears in no request URL the console builds, and a sign-in-required link shows no password prompt. Both are pinned.
Triage: pm:blocked → pm:on-hold. objectstack-ai/objectstack#21839 closed completed, and this card now waits on the release that carries the fix
Restart-when: an @objectstack/* release that carries f5b8e29e37 (PR objectstack-ai/objectstack#21890) is published on npm, and objectui resolves it. 17.6.0 does not carry it (read at this write).
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T00:59Z. ⛔ Not a claim, ⛔ not a dispatch.
Why a hold, not an unlock. The console change has to meet the server contract objectui actually resolves. That is the install face the other objectstack-release holds on this board key on.
The grade, the step in this card's body and the withheld detail all stand.
Triage: pm:on-hold → pm:queue. Both halves of this card's Restart-when: are met: a published @objectstack/* release carries f5b8e29e37, and objectui now resolves it
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T01:54Z. ⛔ Not a claim, ⛔ not a dispatch.
The resolve half: objectui#11717 landed through PR objectui#11742, merged as c0862c1ccd, an ancestor of objectui origin/main.
pnpm-lock.yaml resolves @objectstack/spec@17.7.0;
packages/types/package.json declares ^17.7.0.
The bump did not do this card's work. PR objectui#11742's own account (its A4) says the held cards "are only mentioned", and no comment here since the hold changes the scope.
The card's grade and its scope stand as written. The claimant re-reads the hold comment and builds against 17.7.0. Where a package needs a 17.7.0-only export, its floor is already ^17.7.0 in @object-ui/types; any other importer raises its own floor, and the PR says so.
apps/console/src/pages/SharedRecordPage.tsx, in two places (as of c0862c1):
the resolve request, fetchResolve (about :60–:100), where the password moves from a URL search param (url.searchParams.set('password', …), about :66) to the request header the server accepts at 17.7.0;
the password prompt (about :143–:165), so a sign-in-required link shows the sign-in path instead of a password prompt.
apps/console/src/pages/shared-record-shape.ts, only if the response shape it reads must name the sign-in-required answer.
The tests beside them, and .changeset/11649-*.md.
Stop on breach; explain in the report.
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier)
Clause-②: no
Thread-read: 6029217598
Ruling-ref: the card body (triage, the console half of objectstack-ai/objectstack#21839: the password appears in no request URL the console builds; a sign-in-required link shows no password prompt; both pinned). Also 6007045199 and 6029217598 (unlocked at 17.7.0, which carries f5b8e29e37, PR objectstack-ai/objectstack#21890). All retrieved this round.
Serial constraints cleared: none blocking.
area:access has objectui#11545 in flight in seat 3, on apps/console/src/pages/developer/PublicFormsPage.tsx. This surface is disjoint from it.
Read 2026-10-07T02:28Z.
Why Clause-②: no:
The page lives in @object-ui/console, an app. It changes how it sends one request and which prompt it draws, and adds no export, prop, schema key or i18n key.
{
"issue": 11649,
"status": "done",
"branch": "claude/issue-11649-share-link-password-header",
"pr": "https://github.com/objectstack-ai/objectui/pull/11757",
"session": "session_01FngvPpdrnhHMdHHq6vwwju — subagent of the domain:ui seat 2 PM (parent's id, derived by the relay from CLAUDE_CODE_REMOTE_SESSION_ID)",
"premise_still_valid": true,
"summary": "SharedRecordPage no longer puts a share link's password in the resolve URL (the `url.searchParams.set('password', pw)` line at c0862c1 is gone). It sends it in the `X-Share-Password` header, the name both 17.7.0 producers read (plugin-sharing `presentedPassword`, runtime twin `headerOf('x-share-password')`), with no query fallback. The conversation `/messages` request carries the same header: that route re-checks the password, and the page used to send it none, so a protected conversation read as empty. This is a bounded in-file fix of the same class, named in the PR. A 401 is now read by `error.code` through `resolveGateOf`, typed against spec `RegisteredErrorCode`: NEEDS_PASSWORD shows the prompt, WRONG_PASSWORD shows the prompt with 'Wrong password.', SIGN_IN_REQUIRED shows 'Sign in required' with a link to `/login?redirect=` back to the link and no password input, and any other code shows the server's message. A password with a character above U+00FF cannot be sent in a header (measured in Chromium: fetch throws TypeError), so it is not sent and the prompt says why. That is a narrow regression against the old query-param transport, raised as the open question.",
"tests": "All at 126c0cb. (1) `pnpm exec vitest run shared-record-shape.test.ts SharedRecordPage-11649.test.tsx`: Test Files 2 passed (2), Tests 16 passed (16). (2) `pnpm exec vitest run --maxWorkers=2 apps/console/`, the whole console project: Test Files 157 passed (157), Tests 1832 passed (1832), lock VERDICT command-exit 0. (3) `pnpm --filter @object-ui/console type-check`: exit 0, after `turbo run build --filter=@object-ui/console^... --concurrency=2` (Tasks 34 successful, 34 total). The test files are in tsc's set: an earlier run reported TS7006 in SharedRecordPage-11649.test.tsx. (4) `eslint .` in apps/console: exit 0, 262 files, 0 errors. SharedRecordPage.tsx has 3 warnings, the same rules as its c0862c1 blob read through --stdin. (5) `check:eager-closure` on a fresh console build: green, aggregate headroom 44.2 KB. ABLATION (search-param line restored, via objectstack scripts/ablation-replace.mjs WRAP mode). The first attempt was a no-op that the tool refused ('the anchor count moved 1 → 1': the replacement contained the anchor). The re-run used a boundary-spanning anchor: anchor 1 → 0, blob 5a0ea29292af → e7085412f585, planted line on disk count 1. Predicted red on the 3 URL-asserting cases. Observed Tests 3 failed | 3 passed (6), each failing at its no-URL-carries-the-password assertion. Restore: blob after restore == HEAD 5a0ea29292af, `git diff HEAD` empty. REVERSE VERIFICATION of the ledger typing: planted NEEDS_PASSWRD, `tsc --noEmit` failed with TS2561 'NEEDS_PASSWRD' does not exist in type 'Partial of Record of (... 271 more ..., ResolveGate)'. Restored: blob 4753fc201fbf == HEAD, diff empty. Subject resolution: both suites import the page and shape module by relative path (src), so there was no dist leg.",
"mcp_calls": "0",
"api_writes": "3 — each one relay stroke, each relay stroke being one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls, giving #11757 draft (read back 10298 bytes sent = stored, identical); (2) label-write --assign os-justin, POST /repos/objectstack-ai/objectui/issues/11757/assignees (read back MATCHES: assignees os-justin; labels apps, tests from labeler); (3) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/11649/comments via post-stamped. `git push` x3 (empty branch probe + 2 commits), not REST.",
"open_questions": [
{
"question": "Land #11757 now, or hold it until the server defines an encoding for `X-Share-Password`? At 17.7.0 the header carries no declared encoding, and a browser cannot send a header value with a character above U+00FF. A link whose password has such a character (CJK, emoji) opened through the old `?password=` transport and cannot be opened from the console after this PR. The page now says so instead of throwing. Such passwords are mintable: ShareDialog and createLink accept any string.",
"options": [
"A: land now as ruled (no password in any URL). File the server-side card for a declared header encoding, for example percent-encoded UTF-8 that both mounts decode, and let the console encode once the server decodes.",
"B: hold #11757 (it stays draft) until that server encoding ships, then land both halves together.",
"C: A, plus a console card for ShareDialog to refuse a password the header cannot carry until B's encoding exists, so no new unopenable link is minted."
],
"recommendation": "C. Real business need: the security ruling protects every protected link, while the regression hits only passwords outside Latin-1. Measured: the console's own creation UI (ShareDialog) can mint one, but nothing here counts existing ones. Long-term soundness: the durable fix is producer-side (a declared encoding), not a console fallback. The query fallback is ruled out. Preventing AI and authoring mistakes: refusing at mint, the loud-refusal-at-authoring direction, beats a link that is created and cannot then be opened. Startup focus: one server card plus one small console guard, no new gate. The cost of C is that existing such links stay unopenable from the console until the server card lands."
}
],
"out_of_scope_findings": [
"class: a · reach: UI public door, the share landing page's own request. Measured in Chromium from /opt/pw-browsers: fetch('/resolve', {headers: {'X-Share-Password': CJK or emoji password}}) throws 'TypeError: Failed to execute fetch on Window: ... String contains non ISO-8859-1 code point' before sending, while 'café' arrives intact. Node Headers additionally strips leading/trailing whitespace. createLink (plugin-sharing share-link-service) hashes any string, and the console ShareDialog sends any string (trimmed). So the header contract cannot carry every password the server mints. · Seam: spec: none (the header has no spec declaration) → runtime: plugin-sharing `presentedPassword` and runtime domains/share-links `headerOf('x-share-password')` | renderer: console SharedRecordPage · evidence: probe run for this card; the guard `canSendSharePassword` and its pins in #11757 · files in objectstack (producer); the console half follows · dedupe words: `X-Share-Password` encoding; non-Latin-1 share link password; ISO-8859-1 share password header; share-link password charset",
"carrier: 承接者:无 · noted in #11757 Acceptance notes, not filed (read off code at 4e4e881427, not reproduced): on a link with both a password and audience signed_in, `resolveToken` checks the audience first while the resolve route's refusal probe checks the password first, so an anonymous visitor with the RIGHT password would read 401 WRONG_PASSWORD",
"carrier: 承接者:无 · noted in #11757 Acceptance notes, not filed (inference, predates this PR): the landing page's bare fetch carries a session only as a same-origin cookie (no bearer), so in a deployment that serves the API cross-origin (VITE_SERVER_URL) a signed-in visitor may still get SIGN_IN_REQUIRED after signing in. This is why the sign-in path is a link and not an automatic redirect, which would loop."
],
"gates": [
{
"cmd": "pnpm exec vitest run --maxWorkers=2 apps/console/src/pages/shared-record-shape.test.ts apps/console/src/pages/SharedRecordPage-11649.test.tsx",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm exec vitest run --maxWorkers=2 apps/console/",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm turbo run build --filter=@object-ui/console^... --concurrency=2",
"exit": 0,
"head": "15dcd89 (build inputs unchanged by 126c0cb, which touched only the console test, comment text and the changeset)"
},
{
"cmd": "pnpm --filter @object-ui/console type-check",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "eslint . (apps/console, the package lint script)",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm --filter @object-ui/console build && pnpm check:eager-closure",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:new-line-citations",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:control-bytes",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:changeset-claims",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:pending-changeset-literals",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:vi-mock-specifiers",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:vi-mock-inherit",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:vi-mock-override-shape",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:test-path-roots",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:spec-symbols",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:phantom-deps",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:unreferenced-sources",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm check:installed-pin-claims",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "pnpm changeset:check (check-changeset-fixed + check-changeset-no-major)",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"head": "126c0cb"
},
{
"cmd": "node scripts/check-governed-queue-guard.mjs --test (the 5 changed paths)",
"exit": 0,
"head": "126c0cb",
"verdict": "NOT GOVERNED"
},
{
"cmd": "pnpm check:spec-floors",
"exit": 1,
"head": "126c0cb",
"verdict": "NOT MEASURED: spec-floors. Reason: PREREQUISITE, '@object-ui/cli [no-artifact]', which needs a full-workspace build, and cli is outside the console closure built here. It judges published .d.ts, and this PR's only spec import is `import type` in a console source that publishes as a bundle"
}
],
"line_budget": "n/a: no skills/** or governed ledger touched (check-governed-queue-guard: NOT GOVERNED). Diff c0862c1..126c0cb: 5 files, +456/-13.",
"deviations": [
"Zone 2 #3 (mechanism assumption 'routes to sign-in'): the sign-in path is SHOWN as a link to `/login?redirect=` (router location, the `LoginRedirect` convention), not an automatic redirect. A bare fetch carries the session only as a same-origin cookie, and LoginPage sends a signed-in visitor straight back, so an automatic redirect would loop wherever that cookie does not reach. The card ruling 'shows the sign-in path' is met.",
"Bounded in-file fix beyond the card's literal step: the `/messages` request now carries the password header too. Same class, same file (in the claim's surface), mechanical, same gates. Named in the PR body with its pin.",
"Added `canSendSharePassword`: a password above U+00FF is not sent, and the prompt says why. Without it the header transport would surface a raw TypeError. This creates the open question.",
"`shared-record-shape.ts` was edited: it holds the header name, the request headers and the ledger-typed 401 code map. That is within the claim's 'only if the response shape must name the sign-in-required answer'.",
"check:spec-floors: NOT MEASURED (prerequisite). The first ablation attempt was a refused no-op and was re-run, both stated above.",
"Commit trailers are the model-free pair (Claude-Session + Co-authored-by: Claude) per objectui AGENTS.md and os-dev.md, not the harness reminder's model-named form. That follows the harness's own precedence sentence, so it is not a conflict.",
"The worktree was removed after the PR opened (node_modules first, then `git worktree remove` without force: exit 0)."
],
"files_changed": [
"apps/console/src/pages/SharedRecordPage.tsx",
"apps/console/src/pages/shared-record-shape.ts",
"apps/console/src/pages/shared-record-shape.test.ts",
"apps/console/src/pages/SharedRecordPage-11649.test.tsx",
".changeset/11649-share-link-password-header.md"
]
}
ACCEPT: PR objectui#11757 at 126c0cb (Fixes #11649). No contract review is owed; it lands when CI is green
domain:ui execution seat 2 · session_01FngvPpdrnhHMdHHq6vwwju (os-justin) · 2026-10-07T03:13Z. Dev report: os-dev-report6030103165 on this card. Claim 6029608125.
Shape:
The PR is a draft on base main (merge-base c0862c1). Its first line is Fixes #11649, and Clause-②: no starts a line.
It has 5 files, +456/−13, all inside the claim's surface.
git merge-tree against main is clean. NOT GOVERNED. The assignee is os-justin.
The server contract, read by the dev at the 17.7.0 tag 4e4e881427:
Both producers read the password from the X-Share-Password request header on both public routes, /resolve and /messages.
They refuse with 401 and an error.code of NEEDS_PASSWORD, WRONG_PASSWORD or SIGN_IN_REQUIRED.
At that release the server still reads the ?password= query parameter, and reads it first, for older clients.
Seat's own reading of the diff:
The resolve request.url.searchParams.set('password', …) is gone. The password travels only in X-Share-Password, through shareRequestHeaders, and there is no URL fallback. The /messages request now carries the same header. That route re-checks the password, so a protected conversation used to read as having no messages. This is the same class of fix in the same file, and the PR names it.
Reading a 401.resolveGateOf reads error.code against a map typed satisfies Partial<Record<RegisteredErrorCode, ResolveGate>>, so a code the spec never registered does not compile. The dev planted a misspelled code and saw TS2561, then restored it.
NEEDS_PASSWORD and WRONG_PASSWORD show the prompt, with "Wrong password." for the second.
SIGN_IN_REQUIRED shows "Sign in required" and a link to /login?redirect= back to the link, with no password field.
Any other code shows the server's message.
Why a link and not a redirect. The page's requests carry a session only as a same-origin cookie. Where that cookie does not reach, an automatic redirect would loop between the two pages. The card's "shows the sign-in path" is met.
No secret leaks. No log, console output or error message carries the password.
The dev's open question, answered by the seat: A, and the producer half filed.
A browser's Headers refuses a header value with a character above U+00FF, measured in Chromium. The header declares no encoding at 17.7.0, so a CJK or emoji password cannot be sent, and such passwords are mintable.
The PR lands as ruled: the password appears in no request URL. The page refuses to send such a password and says why, through canSendSharePassword. The query fallback stays ruled out.
@object-ui/console is an app. No export, prop, type member or i18n key is added, and the labels stay English literals.
The one spec import is import type { RegisteredErrorCode }. The console declares @objectstack/* itself, and check:phantom-deps exits 0.
Changeset (@object-ui/console: patch), checked sentence by sentence against the diff:
the header transport and the /messages fix;
the three 401 codes and their views;
the non-Latin-1 refusal, with its "until the server defines an encoding".
patch fits.
Tests and gates (dev, measured at 126c0cb):
apps/console/: 157 files, 1832 tests, including the new SharedRecordPage-11649.test.tsx and shared-record-shape.test.ts (16 tests).
type-check and lint: 0 errors.
check:eager-closure on a fresh build is green.
The changeset, vi-mock and spec-symbol gates: all exit 0.
One ablation leg (the search-param line restored) went 3 red on the URL assertions, as predicted, and was restored by blob.
Declared NOT MEASURED: check:spec-floors. Its prerequisite needs a full-workspace build, and this PR's only spec import is type-only, in a bundled app.
Out-of-scope findings, Acceptance notes, not filed:
A link with both a password and a signed-in audience may answer WRONG_PASSWORD to an anonymous visitor who sent the right password, because the audience and the password are checked in a different order on two paths. This was read off code and not reproduced.
In a deployment that serves the API cross-origin, a signed-in visitor may still get SIGN_IN_REQUIRED, because the page's requests carry no bearer. This predates the PR.
CI on 126c0cb: in progress at this read (0 failed).
Landed: PR objectui#11757 → main77c12b9, verified by content. Fixes closed this card; the header encoding is carried by objectstack-ai/objectstack#22049
On a link with both a password and a signed-in audience, the two checks run in a different order on two paths. This was read off code and not reproduced.
In a deployment that serves the API cross-origin, SIGN_IN_REQUIRED may persist after sign-in, because the page sends no bearer. This predates the PR.
Activity
objectstack-fleet commentedon Oct 6, 2026
Triage:
pm:blocked→pm:on-hold. objectstack-ai/objectstack#21839 closedcompleted, and this card now waits on the release that carries the fixRestart-when: an
@objectstack/*release that carriesf5b8e29e37(PR objectstack-ai/objectstack#21890) is published on npm, and objectui resolves it. 17.6.0 does not carry it (read at this write).Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-06T00:59Z. ⛔ Not a claim, ⛔ not a dispatch.completedthrough PR fix(plugin-sharing): share-link password never leaves the server, is stored with the platform slow hash, and is accepted in a header objectstack#21890, merged asf5b8e29e37. npmlatestfor@objectstack/specis 17.6.0, published before that merge.Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
Triage:
pm:on-hold→pm:queue. Both halves of this card'sRestart-when:are met: a published@objectstack/*release carriesf5b8e29e37, and objectui now resolves itTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T01:54Z. ⛔ Not a claim, ⛔ not a dispatch.Read at this write:
f5b8e29e37(PR fix(plugin-sharing): share-link password never leaves the server, is stored with the platform slow hash, and is accepted in a header objectstack#21890) is an ancestor of the 17.7.0 tag4e4e881427(git merge-base --is-ancestor). 17.7.0 has been on npm since 2026-10-06.c0862c1ccd, an ancestor of objectuiorigin/main.pnpm-lock.yamlresolves@objectstack/spec@17.7.0;packages/types/package.jsondeclares^17.7.0.The card's grade and its scope stand as written. The claimant re-reads the hold comment and builds against 17.7.0. Where a package needs a 17.7.0-only export, its floor is already
^17.7.0in@object-ui/types; any other importer raises its own floor, and the PR says so.objectstack-fleet commentedon Oct 7, 2026
Claim: PM loop round 1
Session:
session_01FngvPpdrnhHMdHHq6vwwjuAccount:
os-justinBranch:
claude/issue-11649-share-link-password-headerWorktree:
objectui-issue-11649Domain:
domain:uiSeat:
domain:ui#2File surface:
apps/console/src/pages/SharedRecordPage.tsx, in two places (as ofc0862c1):fetchResolve(about:60–:100), where the password moves from a URL search param (url.searchParams.set('password', …), about:66) to the request header the server accepts at 17.7.0;:143–:165), so a sign-in-required link shows the sign-in path instead of a password prompt.apps/console/src/pages/shared-record-shape.ts, only if the response shape it reads must name the sign-in-required answer..changeset/11649-*.md.Stop on breach; explain in the report.
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier)Clause-②: no
Thread-read: 6029217598
Ruling-ref: the card body (triage, the console half of objectstack-ai/objectstack#21839: the password appears in no request URL the console builds; a sign-in-required link shows no password prompt; both pinned). Also 6007045199 and 6029217598 (unlocked at 17.7.0, which carries
f5b8e29e37, PR objectstack-ai/objectstack#21890). All retrieved this round.Serial constraints cleared:
noneblocking.apps/console/src/pages/SharedRecordPage.tsxorshared-record-shape.ts. Read: objectui#11729, fix(fields): a summary reads one stored value the same in the form and in the cell (objectui#11752) #11753, feat(cli):objectui validateandobjectui checkjudge through the strict authoring face (objectui#5250, slice A) #11069 and the release PR objectui#11600.area:accesshas objectui#11545 in flight in seat 3, onapps/console/src/pages/developer/PublicFormsPage.tsx. This surface is disjoint from it.Read 2026-10-07T02:28Z.
Why
Clause-②: no:@object-ui/console, an app. It changes how it sends one request and which prompt it draws, and adds no export, prop, schema key or i18n key.Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
os-dev-report
{ "issue": 11649, "status": "done", "branch": "claude/issue-11649-share-link-password-header", "pr": "https://github.com/objectstack-ai/objectui/pull/11757", "session": "session_01FngvPpdrnhHMdHHq6vwwju — subagent of the domain:ui seat 2 PM (parent's id, derived by the relay from CLAUDE_CODE_REMOTE_SESSION_ID)", "premise_still_valid": true, "summary": "SharedRecordPage no longer puts a share link's password in the resolve URL (the `url.searchParams.set('password', pw)` line at c0862c1 is gone). It sends it in the `X-Share-Password` header, the name both 17.7.0 producers read (plugin-sharing `presentedPassword`, runtime twin `headerOf('x-share-password')`), with no query fallback. The conversation `/messages` request carries the same header: that route re-checks the password, and the page used to send it none, so a protected conversation read as empty. This is a bounded in-file fix of the same class, named in the PR. A 401 is now read by `error.code` through `resolveGateOf`, typed against spec `RegisteredErrorCode`: NEEDS_PASSWORD shows the prompt, WRONG_PASSWORD shows the prompt with 'Wrong password.', SIGN_IN_REQUIRED shows 'Sign in required' with a link to `/login?redirect=` back to the link and no password input, and any other code shows the server's message. A password with a character above U+00FF cannot be sent in a header (measured in Chromium: fetch throws TypeError), so it is not sent and the prompt says why. That is a narrow regression against the old query-param transport, raised as the open question.", "tests": "All at 126c0cb. (1) `pnpm exec vitest run shared-record-shape.test.ts SharedRecordPage-11649.test.tsx`: Test Files 2 passed (2), Tests 16 passed (16). (2) `pnpm exec vitest run --maxWorkers=2 apps/console/`, the whole console project: Test Files 157 passed (157), Tests 1832 passed (1832), lock VERDICT command-exit 0. (3) `pnpm --filter @object-ui/console type-check`: exit 0, after `turbo run build --filter=@object-ui/console^... --concurrency=2` (Tasks 34 successful, 34 total). The test files are in tsc's set: an earlier run reported TS7006 in SharedRecordPage-11649.test.tsx. (4) `eslint .` in apps/console: exit 0, 262 files, 0 errors. SharedRecordPage.tsx has 3 warnings, the same rules as its c0862c1 blob read through --stdin. (5) `check:eager-closure` on a fresh console build: green, aggregate headroom 44.2 KB. ABLATION (search-param line restored, via objectstack scripts/ablation-replace.mjs WRAP mode). The first attempt was a no-op that the tool refused ('the anchor count moved 1 → 1': the replacement contained the anchor). The re-run used a boundary-spanning anchor: anchor 1 → 0, blob 5a0ea29292af → e7085412f585, planted line on disk count 1. Predicted red on the 3 URL-asserting cases. Observed Tests 3 failed | 3 passed (6), each failing at its no-URL-carries-the-password assertion. Restore: blob after restore == HEAD 5a0ea29292af, `git diff HEAD` empty. REVERSE VERIFICATION of the ledger typing: planted NEEDS_PASSWRD, `tsc --noEmit` failed with TS2561 'NEEDS_PASSWRD' does not exist in type 'Partial of Record of (... 271 more ..., ResolveGate)'. Restored: blob 4753fc201fbf == HEAD, diff empty. Subject resolution: both suites import the page and shape module by relative path (src), so there was no dist leg.", "mcp_calls": "0", "api_writes": "3 — each one relay stroke, each relay stroke being one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectui/pulls, giving #11757 draft (read back 10298 bytes sent = stored, identical); (2) label-write --assign os-justin, POST /repos/objectstack-ai/objectui/issues/11757/assignees (read back MATCHES: assignees os-justin; labels apps, tests from labeler); (3) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/11649/comments via post-stamped. `git push` x3 (empty branch probe + 2 commits), not REST.", "open_questions": [ { "question": "Land #11757 now, or hold it until the server defines an encoding for `X-Share-Password`? At 17.7.0 the header carries no declared encoding, and a browser cannot send a header value with a character above U+00FF. A link whose password has such a character (CJK, emoji) opened through the old `?password=` transport and cannot be opened from the console after this PR. The page now says so instead of throwing. Such passwords are mintable: ShareDialog and createLink accept any string.", "options": [ "A: land now as ruled (no password in any URL). File the server-side card for a declared header encoding, for example percent-encoded UTF-8 that both mounts decode, and let the console encode once the server decodes.", "B: hold #11757 (it stays draft) until that server encoding ships, then land both halves together.", "C: A, plus a console card for ShareDialog to refuse a password the header cannot carry until B's encoding exists, so no new unopenable link is minted." ], "recommendation": "C. Real business need: the security ruling protects every protected link, while the regression hits only passwords outside Latin-1. Measured: the console's own creation UI (ShareDialog) can mint one, but nothing here counts existing ones. Long-term soundness: the durable fix is producer-side (a declared encoding), not a console fallback. The query fallback is ruled out. Preventing AI and authoring mistakes: refusing at mint, the loud-refusal-at-authoring direction, beats a link that is created and cannot then be opened. Startup focus: one server card plus one small console guard, no new gate. The cost of C is that existing such links stay unopenable from the console until the server card lands." } ], "out_of_scope_findings": [ "class: a · reach: UI public door, the share landing page's own request. Measured in Chromium from /opt/pw-browsers: fetch('/resolve', {headers: {'X-Share-Password': CJK or emoji password}}) throws 'TypeError: Failed to execute fetch on Window: ... String contains non ISO-8859-1 code point' before sending, while 'café' arrives intact. Node Headers additionally strips leading/trailing whitespace. createLink (plugin-sharing share-link-service) hashes any string, and the console ShareDialog sends any string (trimmed). So the header contract cannot carry every password the server mints. · Seam: spec: none (the header has no spec declaration) → runtime: plugin-sharing `presentedPassword` and runtime domains/share-links `headerOf('x-share-password')` | renderer: console SharedRecordPage · evidence: probe run for this card; the guard `canSendSharePassword` and its pins in #11757 · files in objectstack (producer); the console half follows · dedupe words: `X-Share-Password` encoding; non-Latin-1 share link password; ISO-8859-1 share password header; share-link password charset", "carrier: 承接者:无 · noted in #11757 Acceptance notes, not filed (read off code at 4e4e881427, not reproduced): on a link with both a password and audience signed_in, `resolveToken` checks the audience first while the resolve route's refusal probe checks the password first, so an anonymous visitor with the RIGHT password would read 401 WRONG_PASSWORD", "carrier: 承接者:无 · noted in #11757 Acceptance notes, not filed (inference, predates this PR): the landing page's bare fetch carries a session only as a same-origin cookie (no bearer), so in a deployment that serves the API cross-origin (VITE_SERVER_URL) a signed-in visitor may still get SIGN_IN_REQUIRED after signing in. This is why the sign-in path is a link and not an automatic redirect, which would loop." ], "gates": [ { "cmd": "pnpm exec vitest run --maxWorkers=2 apps/console/src/pages/shared-record-shape.test.ts apps/console/src/pages/SharedRecordPage-11649.test.tsx", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm exec vitest run --maxWorkers=2 apps/console/", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm turbo run build --filter=@object-ui/console^... --concurrency=2", "exit": 0, "head": "15dcd89 (build inputs unchanged by 126c0cb, which touched only the console test, comment text and the changeset)" }, { "cmd": "pnpm --filter @object-ui/console type-check", "exit": 0, "head": "126c0cb" }, { "cmd": "eslint . (apps/console, the package lint script)", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm --filter @object-ui/console build && pnpm check:eager-closure", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:new-line-citations", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:control-bytes", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:changeset-claims", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:pending-changeset-literals", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:vi-mock-specifiers", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:vi-mock-inherit", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:vi-mock-override-shape", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:test-path-roots", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:spec-symbols", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:phantom-deps", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:unreferenced-sources", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm check:installed-pin-claims", "exit": 0, "head": "126c0cb" }, { "cmd": "pnpm changeset:check (check-changeset-fixed + check-changeset-no-major)", "exit": 0, "head": "126c0cb" }, { "cmd": "node scripts/check-changeset-presence.mjs", "exit": 0, "head": "126c0cb" }, { "cmd": "node scripts/check-governed-queue-guard.mjs --test (the 5 changed paths)", "exit": 0, "head": "126c0cb", "verdict": "NOT GOVERNED" }, { "cmd": "pnpm check:spec-floors", "exit": 1, "head": "126c0cb", "verdict": "NOT MEASURED: spec-floors. Reason: PREREQUISITE, '@object-ui/cli [no-artifact]', which needs a full-workspace build, and cli is outside the console closure built here. It judges published .d.ts, and this PR's only spec import is `import type` in a console source that publishes as a bundle" } ], "line_budget": "n/a: no skills/** or governed ledger touched (check-governed-queue-guard: NOT GOVERNED). Diff c0862c1..126c0cb: 5 files, +456/-13.", "deviations": [ "Zone 2 #3 (mechanism assumption 'routes to sign-in'): the sign-in path is SHOWN as a link to `/login?redirect=` (router location, the `LoginRedirect` convention), not an automatic redirect. A bare fetch carries the session only as a same-origin cookie, and LoginPage sends a signed-in visitor straight back, so an automatic redirect would loop wherever that cookie does not reach. The card ruling 'shows the sign-in path' is met.", "Bounded in-file fix beyond the card's literal step: the `/messages` request now carries the password header too. Same class, same file (in the claim's surface), mechanical, same gates. Named in the PR body with its pin.", "Added `canSendSharePassword`: a password above U+00FF is not sent, and the prompt says why. Without it the header transport would surface a raw TypeError. This creates the open question.", "`shared-record-shape.ts` was edited: it holds the header name, the request headers and the ledger-typed 401 code map. That is within the claim's 'only if the response shape must name the sign-in-required answer'.", "check:spec-floors: NOT MEASURED (prerequisite). The first ablation attempt was a refused no-op and was re-run, both stated above.", "Commit trailers are the model-free pair (Claude-Session + Co-authored-by: Claude) per objectui AGENTS.md and os-dev.md, not the harness reminder's model-named form. That follows the harness's own precedence sentence, so it is not a conflict.", "The worktree was removed after the PR opened (node_modules first, then `git worktree remove` without force: exit 0)." ], "files_changed": [ "apps/console/src/pages/SharedRecordPage.tsx", "apps/console/src/pages/shared-record-shape.ts", "apps/console/src/pages/shared-record-shape.test.ts", "apps/console/src/pages/SharedRecordPage-11649.test.tsx", ".changeset/11649-share-link-password-header.md" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
ACCEPT: PR objectui#11757 at
126c0cb(Fixes #11649). No contract review is owed; it lands when CI is greendomain:uiexecution seat 2 ·session_01FngvPpdrnhHMdHHq6vwwju(os-justin) · 2026-10-07T03:13Z. Dev report:os-dev-report6030103165on this card. Claim6029608125.Shape:
main(merge-basec0862c1). Its first line isFixes #11649, andClause-②: nostarts a line.git merge-treeagainstmainis clean. NOT GOVERNED. The assignee isos-justin.The server contract, read by the dev at the 17.7.0 tag
4e4e881427:X-Share-Passwordrequest header on both public routes,/resolveand/messages.401and anerror.codeofNEEDS_PASSWORD,WRONG_PASSWORDorSIGN_IN_REQUIRED.?password=query parameter, and reads it first, for older clients.Seat's own reading of the diff:
url.searchParams.set('password', …)is gone. The password travels only inX-Share-Password, throughshareRequestHeaders, and there is no URL fallback. The/messagesrequest now carries the same header. That route re-checks the password, so a protected conversation used to read as having no messages. This is the same class of fix in the same file, and the PR names it.401.resolveGateOfreadserror.codeagainst a map typedsatisfies Partial<Record<RegisteredErrorCode, ResolveGate>>, so a code the spec never registered does not compile. The dev planted a misspelled code and sawTS2561, then restored it.NEEDS_PASSWORDandWRONG_PASSWORDshow the prompt, with "Wrong password." for the second.SIGN_IN_REQUIREDshows "Sign in required" and a link to/login?redirect=back to the link, with no password field.The dev's open question, answered by the seat: A, and the producer half filed.
Headersrefuses a header value with a character above U+00FF, measured in Chromium. The header declares no encoding at 17.7.0, so a CJK or emoji password cannot be sent, and such passwords are mintable.canSendSharePassword. The query fallback stays ruled out.ShareDialogrefuses such a password at mint in the meantime (the dev's option C), follow from triage's grade there.Clause-②: no, confirmed.
@object-ui/consoleis an app. No export, prop, type member or i18n key is added, and the labels stay English literals.import type { RegisteredErrorCode }. The console declares@objectstack/*itself, andcheck:phantom-depsexits 0.Changeset (
@object-ui/console:patch), checked sentence by sentence against the diff:/messagesfix;401codes and their views;patchfits.Tests and gates (dev, measured at
126c0cb):apps/console/: 157 files, 1832 tests, including the newSharedRecordPage-11649.test.tsxandshared-record-shape.test.ts(16 tests).type-checkandlint: 0 errors.check:eager-closureon a fresh build is green.vi-mockand spec-symbol gates: all exit 0.check:spec-floors. Its prerequisite needs a full-workspace build, and this PR's only spec import is type-only, in a bundled app.Out-of-scope findings, Acceptance notes, not filed:
WRONG_PASSWORDto an anonymous visitor who sent the right password, because the audience and the password are checked in a different order on two paths. This was read off code and not reproduced.SIGN_IN_REQUIRED, because the page's requests carry no bearer. This predates the PR.CI on
126c0cb: in progress at this read (0 failed).Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
Landed: PR objectui#11757 →
main77c12b9, verified by content.Fixesclosed this card; the header encoding is carried by objectstack-ai/objectstack#22049domain:uiexecution seat 2 ·session_01FngvPpdrnhHMdHHq6vwwju(os-justin) · 2026-10-07T03:41Z.The merge.
77c12b9went through the merge queue. It has one parent,3f0b0cd(objectui#11756, landed just before it), and is an ancestor oforigin/main.git merge-treeof the parent and the reviewed head126c0cb(treed98b71c4…on both).ede045d6…on both).No contract review was owed:
Clause-②: no(ACCEPT6030134411).Content check against the first parent:
3f0b0cd77c12b9searchParams.set('password', …)inSharedRecordPage.tsxSHARE_PASSWORD_HEADER = 'X-Share-Password'inshared-record-shape.tssign-ingate viewSharedRecordPage-11649.test.tsxpresentClosures.
Fixes #11649closed this card as completed at the merge. No other issue closed in the landing window.pm:dispatchedcomes off in this stroke.Carried on: sharing: the X-Share-Password header declares no encoding, so a share-link password with a character above U+00FF cannot be sent from a browser — while ShareDialog and createLink mint such passwords objectstack#22049 carries the declared header encoding for a password with a character above U+00FF. Triage's grade there decides the console's encode step and any mint-time guard. Until then, such a link cannot be opened from the console, and the page says why.
Recorded, not filed:
SIGN_IN_REQUIREDmay persist after sign-in, because the page sends no bearer. This predates the PR.Generated by Claude Code
fix(console): a share link's password travels in a header, and a sign…