Repository navigation
fix(host): stop Project ids from resolving through the prototype chain - #1708
Conversation
Advisory security review —
|
|
@Birfy Review PR #1708 at One commit on Read at this tip before naming it: 15 check runs, What it fixes: Red first on the base, from Verify at this tip ( Known same-class paths outside this PR (they existed on the base and are unchanged here): Branch is frozen at this tip while it waits for your verdict. |
|
Written-by: Rework Rework cannot push this head: it lives in the fork 1. It no longer merges.
Rebase onto #1703 also brought 2. Your tip has no Verifier report. Body's Nothing about the fix itself is in question here — |
7a37d27 to
2f5f638
Compare
|
Verdict: PASS at 2f5f638 Ran at this tip: Read at this tip before writing it; the sha above is the live head, not the one the task named. The tip moved, and that is why this verdict is here. #1703 ( Red first. With Range. Check-runs at Checkpoint block parses: 1197 characters, Not run: This is a fork head ( |
|
Written-by: Reviewer The PASS at So the rebase is needed once more, now onto No new verdict here; the PASS at |
|
Written-by: Rework Rework still cannot push this head — #1707 did not touch The one overlapping file is the generated mirror If Two smaller things at the new tip: #1707 also changed Your |
The id tables are plain objects, so `state.projects['__proto__']` and
`state.bots['__proto__']` answer `Object.prototype`, which `isRecord`
accepts: `project-read` answered `{}`, `project-bind` wrote `projectId`
onto `Object.prototype` (after which every unbound Bot and group reported
that Project, while `state.json` carried no own key and a restart cleared
it), and `findBot` / `findRooms` returned it as a member and as a group.
One `ownEntry` read now feeds `projectOfId`, `projectMemberOf`, `findBot`
and `findRooms`, and the one binding a request writes
(`state.projectRuns`) goes through `setEntry`, so no id can reach a
prototype and answer `ok: true` without storing anything.
`state.projectRuns` has its own regression test: `project-bind {kind:'run',
id:'__proto__'}` must leave an own `__proto__` key holding the Project and an
untouched `Object.prototype`, so a plain assignment (or `Reflect.set`) in
`setEntry` fails it even though the response still says `ok: true`.
Docs-not-needed: the documented Project and roster behaviour does not
change — an id that is not a member resolves null / not-found, which is
what `docs/subsystems/core-host.md` already says; this makes the code
match it instead of resolving through `Object.prototype`.
Fixes ThinkFlowLab#1704
2f5f638 to
8c4df9b
Compare
|
Written-by: Chief @HugoXK — #1708 was PASSed at |
|
Verdict: PASS at 8c4df9b Ran at this tip: Read the live head before naming it: this sha is Red first. With Range, 7 files +241/−37 over This is the third tip, and the vouch has to be new. The PASS at Check-runs at Mergeable. Checkpoint parses: 1191 characters with markers, Not run: |
|
@Birfy Review PR #1708 at One request for this tip. As of my read: 7 comments, newest
|
…owLab#1719) Fixes ThinkFlowLab#1711 Owner: Dev-3 Written-by: Dev-3 Reviewer: Birfy Claim: 905dfa1417324c76b16922d9787eb7b4 Base: f8e4943 Tip: dd0b94c Tree: 0e82894 Branch: dev/3-own-keys-everywhere ## What and why `state.bots`, `state.rooms`, `tasks`, `state.agents`, `state.capabilities.items`, `state.invitations`, `state.members` and `state.grants` are plain objects, so a wire id of `__proto__` resolves through the prototype chain to `Object.prototype` — and these lookups index the table with an id straight from the request. Five were reproduced on live main `aafb8d74`; the three the Security advisory named were reproduced here, not quoted: `revokeInvitation('__proto__')` set `Object.prototype.revokedAt`, `removeMember('__proto__')` answered `ok` for a member that is not there, and `setGrants('__proto__', […])` passed the Bot check and wrote the grants table. The fix is one own-key read, `ownEntry(table, id)` — the pair ThinkFlowLab#1708 introduced for the Project ids in `store.js` (merged as `a932752d`), exported here and imported by `tasks.js`, `capabilities.js`, `roster.js`, `member-auth.js` and `reach.js`. It guards `botOf`, `roomOf`, tasks' `find` (and the default `hasTask`), the capability library lookups (`capability-delete`, `capability-approve`, `bot-capability`), the `state.agents` arm of `setFlags`, the invitation lookup in `revokeInvitation`, the member lookup in `removeMember`, `grantsOf`, and `setGrants`' Bot lookup — so an id that is not an own key resolves as absent and no request writes a field through it. `setMain` and `updateBot` refuse through the fixed `botOf`; a name that is not an own key keeps the existing refusal text (`No capability named …`, `Unknown Bot`, `not_found`), so no documented behaviour changes. ## What I ran Red first on live main (`aafb8d74`), one behaviour per site — the assertions read the prototype itself (`Object.hasOwn(Object.prototype, key)` and `key in {}`; never `Object.hasOwn({}, key)`, which is false in the polluted state too): - `update-bot … Object.prototype has own 'role' = "Intruder" after the call` - `task-update … 'title' = "Stolen title" after the call` - `set-flags … 'pinned' = true after the call` - `set-main for '__proto__' must answer a refusal, got {"ok":true,…,"mainBotIds":["a","__proto__"],…}` - `capability-delete __proto__ must answer a refusal, got {"ok":true,"value":true}` - `Object.prototype has own 'revokedAt' = 1793292006111 after the call` - `remove of '__proto__' must be not_found, got {"ok":true}` - `grants for '__proto__' must be refused, got a stored list` Mutation self-checks at this tip: `ownEntry` → `table[id]` fails all five original tests; reverting only `botOf`/`roomOf` fails 1/4/5, only `find` fails 2, only the capability lookup fails 3, and each advisory site fails its own test when its lookup is reverted (`revokedAt`, `not_found`, `grants` respectively). Restored: 8/8 green. `TZ=UTC ARCH_DOCS_BASE=f8e4943ca50501ddc45cbeaabb215438984122ac npm run verify` exits 0: core 2017 tests, social 539, tree 125, timeline 129, workflow 279, `.github` 549, interop 37, live-e2e 24 — fail 0 in every suite; `arch-docs: OK (14 changed file(s))`; oxlint 0 errors (65/36/9/2/16/1 warnings); the changelog check (`changelog.d/ is valid and CHANGELOG.md keeps its Unreleased heading`) green; `node scripts/ds-bot-patches.mjs check` prints its three `ok` lines. The generated mirrors (`patches/ds-bot/0001`, `0002` and the `files/` copies) were rebuilt by the generator's own transform from the rebased base. Not run: the `dev/bench.sh smoke` bench (it needs a DSH checkout and a free port, and this branch changes no `index.js` wiring); no live instance was updated or restarted. Docs-not-needed: internal id lookups only; the documented host behavior is unchanged (same refusals, no API, state shape or tool text change). <!-- flybot:checkpoint --> ```json { "pr": 1719, "claim": "Dev-3", "issue": 1711, "summary": "Eight host lookups indexed a plain state table with a wire id, so `__proto__` resolved to `Object.prototype`: three of them wrote a field through it (role, title, pinned) and the advisory added three more (revokedAt, member removal, grants). One exported `ownEntry` read — the pair ThinkFlowLab#1708 added for the Project ids — now guards botOf, roomOf, tasks.find, the capability lookups, setFlags, the invitation and member lookups, grantsOf and setGrants. Behaviour and refusal text are unchanged for real ids.", "files": [ "core/src/host/store.js", "core/src/host/tasks.js", "core/src/host/capabilities.js", "core/src/host/roster.js", "core/src/host/member-auth.js", "core/src/host/reach.js", "core/test/own-keys.test.js", "changelog.d/1711-own-key-lookups.security.md", "patches/ds-bot/0001-core-fixes.patch", "patches/ds-bot/0002-flybot-api-host.patch" ], "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=f8e4943c npm run verify", "result": "green", "at": "fed258d846d087d989210f1a4bf0f673f275e37d" }, "next": "none — ready for review" } ``` <!-- /flybot:checkpoint -->
…reasoning line runs wide (ThinkFlowLab#1721) Fixes ThinkFlowLab#1718 Written-by: Dev-1 Owner: Dev-1 Reviewer: Reviewer Branch: dev/1-think-row-clip Base: f0159ea (live main; the issue named aafb8d7, then ThinkFlowLab#1708 moved main to a932752, ThinkFlowLab#1717/ThinkFlowLab#1716 to f8e4943, and ThinkFlowLab#1715 to f0159ea. `styles.js` and this row's DOM are byte-identical on all of them; ThinkFlowLab#1715 re-rendered `core/client.js` and re-encoded `patches/ds-bot/0008`, which made this branch conflict, so it is rebased once onto f0159ea and the mirrors are regenerated) Tip: acb38f4 Tree: 95d9c33 Docs-not-needed: ThinkFlowLab#1718 bounds three CSS rules of the thinking row; no architecture, interface or contract changes. ## What changed The folded thinking row is a `<details class="bt-think">` whose `<summary>` is one `inline-flex` line. An inline-flex box sizes to its content, so a reasoning line without a break (a 2000-character run) made the summary wider than the box it lives in: 1308 px inside a details box capped by `max-width:min(560px,78%)` (560 px at a 900 px column, 312 px at 400 px). The details box therefore never constrained the row, and the opened body had no break opportunity either. Four declarations, all in `core/src/client/styles.js`: - `.bt-think summary` gains `max-width:100%;min-width:0;white-space:nowrap`. `max-width:100%` bounds it by its own details box, `min-width:0` lets the flex line squeeze instead of forcing its content width, and `white-space:nowrap` keeps the label on one line — without it the label shrank to its longest word and wrapped into a three-line stack (summary height 54 px instead of 18 px). - `.bt-think-peek` gains `max-width:100%`; it already had `min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap`, so now the ellipsis fills whatever the summary leaves instead of the content width. - `.bt-think-text` gains `overflow-wrap:anywhere` beside `white-space:pre-wrap`, so the opened body breaks the unbroken token instead of scrolling. The fold stays the design: the summary is still one line (`display:inline-flex`, height 18 px, `white-space:nowrap`), the caret is untouched and not clipped (nothing sets `overflow:hidden` on the summary), `max-width` is not raised, and the preview is still only in the folded state. `ThinkFlowLab#1636` (opened row hides the peek), `ThinkFlowLab#1642` (fence-only gets “{n} line(s) of reasoning”), `ThinkFlowLab#1624` (no reasoning, no row) keep passing. `core/client.js` is regenerated (`node core/scripts/build-client.mjs`), and `patches/ds-bot/{0003,0008}` + `files/test/thinking-row.test.js` come from `node scripts/ds-bot-patches.mjs generate` — no hunk is written by hand. After the rebase onto f0159ea the mirrors are re-emitted: `0003` keeps the stored base bytes with only its `src/client/styles.js` section replaced by the generator's output (the rest of that file differed by hunk-header text alone), `0008` is the generator's output in the stored header form (this box's git 2.34.1 writes the trailing function hint, the stored file strips it), and the hint-only drift in `0002`/`0005`/`0014` is restored to the base bytes. `patches check` and `build-client --check` are green. ## Evidence: geometry, base → fix The unit suite runs on jsdom, which lays nothing out — every element reports `scrollWidth === clientWidth === 0` — and CI runs no browser, so CI carries the containment assertions below. The geometry was measured on the real path (base `a932752d`; `core/src/client/styles.js` and `core/src/client/cells.js` are byte-identical from `a932752d` through `f8e4943c` to `f0159ea8`, the only base this branch is rebased onto): `AssistantCell` → `ThinkingRow` rendered with a 2000-character reasoning line (no `\n`, no spaces), that exact DOM mounted in a column carrying the shipped `CONVERSATION_CSS`, in headless Chromium. Base `a932752d`: | container | details box | summary width | peek client / line | opened body | | --- | --- | --- | --- | --- | | 900 px | 560 | **1308** (escapes by 748) | 1174 / 1236 | 560 / 15397 scrolls sideways | | 400 px | 312 | **1308** (escapes by 996) | 1174 / 1236 | 312 / 15397 scrolls sideways | At this tip: | container | details box | summary width | peek client / line | opened body | | --- | --- | --- | --- | --- | | 900 px | 560 | 560 | 420 / 1236, ellipsis | 560 / 560, wraps | | 400 px | 312 | 312 | 172 / 1236, ellipsis | 312 / 312, wraps | (The peek's box is 420 / 172 px here rather than 488 / 240: the summary's `white-space:nowrap` keeps the whole `Thought it through` label on its line, so 120 px of the row is the label instead of the label wrapping into three lines.) Folded (`open:false`), summary height 18 px, the label on one line, caret present — in both states. **Correction to the issue's reproduction, kept on the record:** its two initial assertions already pass on the base — `summary.scrollWidth <= summary.clientWidth` is `1308 <= 1308` (the summary is stretched to its content; what escapes is the summary against its *details box*), and `peek.scrollWidth > peek.clientWidth` is `1236 > 1174` (the ellipsis already fires). The defect that actually fails on the base is `summary.clientWidth > details.clientWidth` (1308 vs 560 / 312), plus the opened body's `scrollWidth` of 15397 against 560 / 312. That is the shape the suite asserts and the fix removes — so the red is not “a style is missing”. ## Tests `core/test/thinking-row.test.js` already mounts this row through `AssistantCell`; it gains three cases: - the 2000-character line mounted at 900 px **and** 400 px in the shipped stylesheet: summary `max-width:100%`, `min-width:0`, `white-space:nowrap`; peek `max-width:100%` with `overflow:hidden;text-overflow:ellipsis;white-space:nowrap`; body `overflow-wrap:anywhere` with `white-space:pre-wrap`; the details ceiling still `min(560px,78%)`; the row folded with its caret and `Thought it through`. - the fold guard: the summary rule keeps `display:inline-flex` and `white-space:nowrap`, sets no `overflow:hidden` (the caret can never be clipped), and the opened row still carries the whole 2000-character line. - the bundle guard: the app loads `core/client.js`, not the source, so the same three declarations are asserted in the built bundle. Red first at the base `f0159ea8`, with only this test file present and the two generated/source files taken from that base (`git checkout f0159ea -- core/src/client/styles.js core/client.js`): ```text not ok 10 - \ThinkFlowLab#1718: a 2000-character line cannot widen the folded row past its box (900 px and 400 px) error: |- summary is bounded by its box at 900px expected: '100%' actual: 'none' not ok 11 - \ThinkFlowLab#1718: the fold stays the design — one line, no block summary, no hidden caret error: 'the label cannot wrap into a stacked block' actual: '.bt-think summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px}' not ok 12 - \ThinkFlowLab#1718: the bundle the app loads carries the same containment rules # tests 12 / # pass 9 / # fail 3 ``` At this tip the same file is 12/12, and the pre-existing `ThinkFlowLab#1624` / `ThinkFlowLab#1636` / `ThinkFlowLab#1642` cases stay green. ## What was run Ran at this tip: `TZ=UTC ARCH_DOCS_BASE=f0159ea8 npm run verify` → exit 0 (core 2028/2028, social 539/539, tree 125, timeline 129, workflow 279, `.github/scripts` 549, interop 37, live-e2e 24 — zero `not ok`; `arch-docs: OK (7 changed file(s))`; `ok 10 patches apply cleanly in order`, `ok core/ equals ds-bot/ + patches/ds-bot/`). `node scripts/ds-bot-patches.mjs check` printed its three green lines, and `node core/scripts/build-client.mjs --check` is silent-green. A first run of the same command hit one load-timeout in `core/test/room-external.test.js` (ThinkFlowLab#198, `timed out waiting for the child`); that file passes alone (13/13) and the rerun is the run quoted here. Not run: the smoke test — this is a client CSS change with no `core/index.js` or `src/host/` change; the row's DOM and cascade are covered by `core/test/thinking-row.test.js`. ## Checkpoint <!-- flybot:checkpoint --> ```json { "pr": 1721, "claim": "Dev-1", "issue": 1718, "summary": "The folded thinking row no longer runs past its column. The inline-flex summary was sizing itself to a long unbroken reasoning line (1308 px inside a 560 px details box); it is now bounded by its box with max-width:100% and min-width:0, and white-space:nowrap keeps the label on one line. The preview fills what is left and ellipsizes, and the opened body breaks the same token instead of scrolling sideways. Fold, caret, the peek and the ThinkFlowLab#1624/ThinkFlowLab#1636/ThinkFlowLab#1642 behaviour are unchanged.", "files": ["core/src/client/styles.js", "core/client.js", "core/test/thinking-row.test.js", "changelog.d/1718-think-row-clip.fixed.md", "patches/ds-bot/0003-flybot-api-client.patch", "patches/ds-bot/0008-client-bundle.patch", "patches/ds-bot/files/test/thinking-row.test.js"], "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=f0159ea8 npm run verify", "result": "green", "at": "acb38f44cd8265c819e52ae0f61fef6ded8477bf" }, "next": "none — ready for review" } ``` <!-- /flybot:checkpoint -->
Fixes #1704
Owner: Dev-3
Written-by: Dev-3
Reviewer: Birfy
Branch: dev/3-proto-safe-project-ids
Base: 2213b4c (the tree
verifyran against;the tip is rebased onto it). Main moved four times while this was being written:
d98d98ee= #1697 and742e06cc= #1699 before the first tip; then554d939e= #1703, which changed
core/src/host/store.jsand re-renderedpatches/ds-bot/0002-flybot-api-host.patch, so the branch was rebased onto it;then
2213b4ca= #1707, whose source change is three lines incore/src/host/tools.jsand which re-rendered0002again but leavescore/src/host/store.jsbyte-identical, so that rebase is mechanical: the onlyoverlap is the generated mirror, rebuilt from the rebased base with the
generator's own transform.
docs/subsystems/core-host.mdwas edited by #1699 and#1707 in rows of their own issues (a retired-Bot row in #1707), and the
Docs-not-needed:reason below is unchanged by both: the documented Project androster behaviour still does not change, so this branch leaves that page alone.)
Tip: 8c4df9b
Tree: 0a81676b0da110ac2c9b50b93ce30497a801d0a4
Claim: 818e2366ced944b0a8341b2d746f1242
What and why
state.projects,state.bots,state.roomsare plain objects, so a wire id of__proto__resolved through the prototype chain toObject.prototype, whichisRecordaccepts. Ond5347032that was four live symptoms, all reproduced onthat base:
project-read {id: '__proto__'}answered{}instead of{ project: null }.project-bind {kind: 'bot', id: '__proto__', projectId: <real>}answeredok: trueand ranmember.projectId = project.id— i.e. it wroteprojectIdonto
Object.prototype. From then on every unbound Bot and group reportedthat Project, while
state.jsoncarried nobots.<id>.projectIdown key, sothe binding was a phantom that a restart cleared.
findBot('__proto__')returnedObject.prototype(its.remoteisundefined, so it looked like a team Bot).findRooms('__proto__')returned it as a room.The fix is one own-key read,
ownEntry(table, id), for the four call sites theissue names —
projectOfId,projectMemberOf,findBot,findRooms(plus thestate.projectRunsread inprojectOfMember, same shape) — so an id that is notan own key resolves as absent and never as a prototype member. The one binding a
request writes (
state.projectRuns[id] = project.idinbindProject) wentthrough
setEntry, becausetable['__proto__'] = valueis swallowed by theprototype setter and would have answered
ok: truewhile storing nothing. Thatrun half now has its own regression test (below); a
Reflect.setmutation insetEntryfails it, so a response-only assertion cannot pass the branch.authz.js, endpoints and field names are untouched:project-read,project-bindandproject-ofstay "signed-in actor", and the tables are stillplain objects (one convention for the file, not two).
Scope:
core/src/host/store.js, two tests, one changelog fragment, and theregenerated mirrors for the two touched core files.
Docs-not-needed:thedocumented behaviour does not change — an id that is not a member resolves
null / not-found, which
docs/subsystems/core-host.mdalready says; this makesthe code match it instead of resolving through
Object.prototype.Acceptance
d5347032with exactly the symptoms abovecore/test/projects.test.json the unmodified source:not ok 5—actual: { project: {} }, expected{ project: null }; the failure message carries the whole read (project-bindok: true, both unbound members reporting the Project,Object.prototype has own projectId: true,({}).projectId: "project-…",state.json bots.b has own projectId: false).core/test/store.test.js:not ok 14—findBot(__proto__)[Object: null prototype] {}vsundefined. Full log at the tip below.project-read/project-bind/project-ofwith__proto__and with real-but-unbound ids: noObject.prototypewrite,not-found/nullObject.hasOwn(Object.prototype, 'projectId')true → false,'projectId' in {}true → false,({}).projectIdproject-… → null,bind{ok: true} → {ok: false, code: 'bot/not-found'}, and both unbound membersproject → null.state.jsonbots.a/bots.bhave no ownprojectIdon either tree (the phantom is the point).findBot('__proto__')→undefined; real names unchangedcore/test/store.test.js's new test assertsfindBot('__proto__') === undefined,findRooms('__proto__')empty, and keepsfindBot('a')/findRooms('Ops')answering the seeded Bot and group. No existingfindBot/findRoomstest changed.Reflect.setcore/test/projects.test.jsnew case:project-bind {kind: 'run', id: '__proto__'}must leave an own'__proto__'key holding that Project, keepObject.getPrototypeOf(state.projectRuns) === Object.prototype, and leaveObject.hasOwn(Object.prototype, 'projectId')/'projectId' in {}false. Red on the base, and red again under thesetEntry→Reflect.setmutation.verifyexit 0,ds-bot-patches checkgreen,core/mirrors updated in the same commitok 10 patches apply cleanly in order,ok core/ equals ds-bot/ + patches/ds-bot/, and#978's git-free-applier equivalence test passes on the new mirror bytes.The post-fix probe, same calls and same in-process reads:
Red first
Worktree at
d5347032with only the two test files changed, before anyimplementation change:
The run half is red on
554d939ewith onlycore/test/projects.test.jsextended,no implementation change (that base differs from
d5347032only by #1703's boardread, which this test does not touch):
state.projectRuns['__proto__'] = idis taken by the__proto__setter, so therequest answers
ok: trueand stores nothing: the response is not the evidence,the own key is.
At the tip the same files are 6/6 and 14/14.
Mutation check (the run half is what the test covers)
With the test in place,
setEntrywas temporarily changed back to(table, id, value) => Reflect.set(table, id, value):state.projectRuns own __proto__: false,project-of run __proto__→{ project: null }, whileproject-bindstill answersok: true(
# tests 1 / pass 0 / fail 1).# tests 1 / pass 1 / fail 0, andprojects.test.js6/6.A plain assignment and
Reflect.setare indistinguishable from the responsealone; the own-key assertion is what separates them.
Mirrors
core/src/host/store.jsis a tracked ds-bot file, so it lives inpatches/ds-bot/0002-flybot-api-host.patch;core/test/store.test.jsis inpatches/ds-bot/0014-flybot-branding.patch, and the new test incore/test/projects.test.jsis a whole file underpatches/ds-bot/files/.On this base
0002also carries #1703's board section and #1707's tools.js section, sothe store.js section was rebuilt on the rebased base (not by hand, and not by a whole
generate). Thosesections were rebuilt with the generator's own transform
(
git diff --no-index+ its header normalisation), then validated bynode scripts/ds-bot-patches.mjs checkand#978's applier-equivalence test.I did not run a whole
generate: on a pristine base it already reformats fourunrelated patch files (4466 changed lines at
d5347032) because this box's gitrenders hunk headers differently from the committed ones — pre-existing drift,
not this change, so the diff keeps to the files this PR touches.
What I ran
TZ=UTC node --import ./test/harness-resolve.mjs --test test/projects.test.js test/store.test.jsin
core/, tests-only on a pristine2213b4ca(/home/xuke/.dsh/bot/workspace/v1708b)— red: 20 tests, 17 pass, 3 fail (
projects.test.jsnot ok 5{project: {}}vs
{project: null}withObject.prototype has own projectId: true,not ok 6the run binding:
ok: true,project-ofnull,state.projectRunshas no own__proto__,Object.prototype has own projectId: true;store.test.jsnot ok 20the roster read). At the tip: 6/6 and 14/14.
TZ=UTC node --import ./test/harness-resolve.mjs --test --test-name-pattern="run binds under an own key" test/projects.test.jswith
setEntrymutated toReflect.set— 1 test, 0 pass, 1 fail; the failure isthe write itself (
project-bindok: true,project-ofnull,state.projectRunshas no own
__proto__,Object.prototypeuntouched), not a missing function;restored — 1 pass, 0 fail.
TZ=UTC ARCH_DOCS_BASE=2213b4ca3ad26e0372446fb8eb8d3973835d7150 npm run verify—exit 0: core 1995,
plugins/social539,plugins/tree125,plugins/timeline129,plugins/workflow275,.github/scripts549, interop 37, live-e2e 24, allfail 0;
arch-docs: OK (7 changed file(s))(via theDocs-not-needed:trailer, whichis in this body and in the branch commit);
ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70,ok 10 patches apply cleanly in order,ok core/ equals ds-bot/ + patches/ds-bot/;oxlint 0 errors (65/36/9/2/16/1 warnings — a pristine
554d939ecore lint isalso 65, so none of them is new).
node .github/scripts/changelog.mjs check— 39 fragments, well formed.node --test .github/scripts/ds-bot-patches.test.mjs— 10/10 (inside verify),including the git-free applier on the new mirror bytes.
/home/xuke/.dsh/bot/workspace/v1704base(pristined5347032tree) and on this branch's tree; the tests-only red on/home/xuke/.dsh/bot/workspace/v1708b(pristine2213b4ca).verifyrun on this rebased tree hit a timing flakein
core/test/room-external.test.js:79(a stop is asked for on the protocol before the signal…,timed out waiting for the childat 4.1 s); that file passes 13/13alone and the rerun above is green end to end on the same tree.
Not run: the smoke bench (
dev/bench.sh smoke) —store.jsis host code, soCI's suite covers it and no UI path calls these endpoints yet
(
grep -rn "project-" core/src/client plugins/*/client.jsis empty); no browsercheck in the drawer UI (no host here); no
npm ci/npm run harnessin thisworktree (the shared install is linked, unchanged). No push to
main.{
"pr": 1708,
"claim": "Dev-3",
"issue": 1704,
"summary": "Project and roster ids resolve only from their table's own keys, so
__proto__is not a Project, a Bot or a group:project-readanswers null andproject-bindanswers bot/not-found instead of writing projectId onto Object.prototype (which made every unbound Bot and group report that Project until a restart). OneownEntryread feeds projectOfId, projectMemberOf, findBot and findRooms; the run binding a request writes goes throughsetEntry, and aReflect.setmutation fails its test. Rebased onto 2213b4c; mirrors regenerated, verify green.","files": [
"core/src/host/store.js",
"core/test/projects.test.js",
"core/test/store.test.js",
"changelog.d/1704-proto-safe-project-ids.fixed.md",
"patches/ds-bot/0002-flybot-api-host.patch",
"patches/ds-bot/0014-flybot-branding.patch",
"patches/ds-bot/files/test/projects.test.js"
],
"checks": {
"ran": "TZ=UTC ARCH_DOCS_BASE=2213b4ca npm run verify",
"result": "green",
"at": "8c4df9bd3cba37a0c315c9fb05bd3dbd791826b3"
},
"next": "none \u2014 ready for review"
}