Repository navigation
feat(file-safety): guard machinery in the writer, with the identity and rights refusals (U5-B, split 1/2 of #1914, #1375) #2357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
easonLiangWorldedtech
wants to merge
75
commits into
Zoo-Code-Org:main
Choose a base branch
from
easonLiangWorldedtech:vps2f-1914-u5b
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+6,368
−60
Open
Changes from all commits
Commits
Show all changes
75 commits
Select commit
Hold shift + click to select a range
1823e82
split unit U1 of PR 1833 (issue 1375)
bef023d
fix(file-safety): close the pre-merge findings on the publish primiti…
12b57c1
fix(file-safety): propagate a non-ENOENT lstat failure in resolvePubl…
984c6ed
fix(file-safety): keep the rollback pair typed and the mock stand-ins…
20136ce
rebuild unit u2 on the fixed chain
b524863
chore(lint): prune the safeWriteJson suppression this unit earns
93329bd
rebuild unit u3 on the fixed chain
0f32b3e
fix(task): declare the observation registry on Task in this unit
8372003
rebuild unit u4 on the fixed chain
074348e
fix(file-safety): inherit unit 1 committed guard and exact rmdir asse…
aaf2434
fix(tools): stop the source indentation leaking into the clipped-line…
d9ee95a
fix(file-safety): keep the publish error message in RollbackFailureError
fedb77d
test(file-safety): assert the exact staging directory removed after a…
28737be
test: re-trigger required checks - the queued runs were cancelled by …
e53a6e0
fix(file-safety): give the Windows DACL dump a per-write name
bb34958
fix(tools): the clipping notice must describe the slice it actually r…
62705bb
fix(file-safety): keep the target present while publishing (durable c…
b894375
feat(utils): let a caller confine a write to a directory
b9cfd73
fix(file-safety): do not read a failed target lstat as a missing target
afdd517
fix(file-safety): compare staging and target identity with bigint stats
13c2902
test(file-safety): pin the bigint options in the staging-identity tests
2955c80
fix(file-safety): report a Windows replacement whose DACL was not pre…
a6dc245
fix(file-safety): keep DACL warning delivery from failing the save
bfff215
fix(utils): check confinement before taking the advisory lock
677478f
fix(file-safety): handle async warning sinks and confine before mkdir
bcce2c3
test(file-safety,utils): cover the commit-rename failure and unmock w…
4291ced
test(task): pin that each Task owns its observation registry
40b9c39
fix(mcp): confine project MCP writes to the workspace root
d5ff976
fix(task,tools): retire the observation registry on disposal and pin …
ae8fda5
fix(u4): re-check cancellation after the post-read stat before observing
0deb7f4
fix(file-safety): one lock key whether the parent directory exists yet
ddae96e
test(file-safety): a backup copy that fails part-way must not survive…
e12880f
chore(ci): no-op commit to re-trigger the review for this head
7bcc158
test(tools,mcp): count the observation stats and name the pinned write
ce174a2
style(test): reflow three pre-existing lines to the repo prettier gate
25b02ca
style(tools,file-safety,utils): apply the repo prettier gate to the s…
cc76cbf
test(misc): teach the Unicode reader spec the hasClippedLines field
5a6b43e
fix(tools): drop the duplicate Task import the merge produced
f5a2a50
fix(tools): correct the merged no-explicit-any count for the read-fil…
ea00f9f
fix(utils): keep confinement active for a declared-but-empty confineTo
325a0a2
docs(file-safety): narrow what confineTo claims to what it can prove
0fe578c
fix(file-safety): refuse a publish whose Windows DACL cannot be prese…
d73231e
style(tools): keep the merged spec import block prettier-clean
easonLiangWorldedtech 56fe7a0
fix(u4): align the unit with main's unused-vars re-enable
easonLiangWorldedtech 1f4169e
chore: drop the commit-message scratch file that slipped into the tree
easonLiangWorldedtech fcadb91
fix(safe-write): fail closed on confinement escapes and share the loc…
easonLiangWorldedtech a53d162
fix(read-file): flag clipped lines in indentation reads and report th…
easonLiangWorldedtech 0cc6a0a
fix(file-safety): undo an escaped publish only when the file is still…
easonLiangWorldedtech e6b9f65
style: apply prettier formatting to the escape-undo helper
easonLiangWorldedtech 3f8f9d7
fix(safety): read the staged identity bytes only when confinement can…
easonLiangWorldedtech c0120aa
Merge remote-tracking branch 'upstream/main' into HEAD
easonLiangWorldedtech ef1044f
chore(lint): drop the unused pathUtils import in the McpHub spec
easonLiangWorldedtech 011451e
fix(mcp): read the project root once per write; make the cancel tests…
easonLiangWorldedtech f60aaeb
Merge upstream/main into fws/u4-read-scope-recording
5e45671
fix(merge): restore the upstream hunks dropped by the main merge
easonLiangWorldedtech a15ada1
fix(file-safety): propagate the staged-byte read error before publishing
easonLiangWorldedtech 5937180
fix(file-safety): run the escape re-check after a post-commit teardow…
easonLiangWorldedtech 9787df6
fix(file-safety): re-derive the lock key once the publish target reso…
easonLiangWorldedtech 9ed41eb
fix(file-safety): confirm the published file identity before the esca…
easonLiangWorldedtech e62f198
test(file-safety): account for the post-lock key re-derivation in the…
easonLiangWorldedtech 4391a8b
Merge upstream/main (73931fe8) into fws/u4-read-scope-recording
easonLiangWorldedtech 279e77d
Reconcile the carried lock-key spec with the replacement protocol
easonLiangWorldedtech c0b57f7
Mark safeWriteText internal now that nothing in production calls it
easonLiangWorldedtech e3d6327
Add the notice-separator row the review asked for on the clipped full…
easonLiangWorldedtech 1871993
Read the staged bytes after staging, where the escape undo can use them
easonLiangWorldedtech 7bf2654
Copy the target's mode onto the staged file so the publish cannot wid…
easonLiangWorldedtech f739a5f
Compare the interruption injection against identity so main's rows fi…
easonLiangWorldedtech b86511f
Repair the mode-read row I added: it pinned the caller's path spelling
easonLiangWorldedtech 416179d
refactor(u4): reuse main's errorCode helper and drop a one-line fsync…
easonLiangWorldedtech 120cb05
unit(u5b): guard machinery in the writer, with the identity and right…
easonLiangWorldedtech 5841dd2
test(utils): key the injected backup-stat failure on the file, not on…
easonLiangWorldedtech b328ded
test(u5b): scope the injected stat to the row that installs it
easonLiangWorldedtech 8a9d5b7
fix(u5b): refuse an exact backup mtime tie instead of trusting readdi…
easonLiangWorldedtech 87d4aff
fix #2357: make the failure path report what it could not clean up
easonLiangWorldedtech 25a1ad6
docs #2357: say what the backup option and a DACL refusal actually pr…
easonLiangWorldedtech File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| import { describe, it, expect, vi } from "vitest" | ||
|
|
||
| import { ObservationRegistry } from "../observationRegistry" | ||
|
|
||
| describe("ObservationRegistry", () => { | ||
| it("observe → get returns the recorded version and observedAt", () => { | ||
| const reg = new ObservationRegistry() | ||
| reg.observe("/a/b/c.ts", "1:2:300:4000000000:5000000000") | ||
|
|
||
| const obs = reg.get("/a/b/c.ts") | ||
| expect(obs).toBeDefined() | ||
| expect(obs!.version).toBe("1:2:300:4000000000:5000000000") | ||
| expect(typeof obs!.observedAt).toBe("number") | ||
| }) | ||
|
|
||
| it("re-observe replaces the entry with a fresh observedAt", () => { | ||
| vi.useFakeTimers() | ||
| const reg = new ObservationRegistry() | ||
| reg.observe("/a/b/c.ts", "v1") | ||
| const first = reg.get("/a/b/c.ts")! | ||
| expect(first.version).toBe("v1") | ||
|
|
||
| vi.advanceTimersByTime(50) | ||
| reg.observe("/a/b/c.ts", "v2") | ||
| const second = reg.get("/a/b/c.ts")! | ||
| expect(second.version).toBe("v2") | ||
| expect(second.observedAt).toBeGreaterThan(first.observedAt) | ||
|
|
||
| vi.useRealTimers() | ||
| }) | ||
|
|
||
| it("has returns true for observed paths, false otherwise", () => { | ||
| const reg = new ObservationRegistry() | ||
| reg.observe("/x.ts", "t1") | ||
| expect(reg.has("/x.ts")).toBe(true) | ||
| expect(reg.has("/y.ts")).toBe(false) | ||
| }) | ||
|
|
||
| it("size reflects the number of observed entries", () => { | ||
| const reg = new ObservationRegistry() | ||
| expect(reg.size).toBe(0) | ||
| reg.observe("/a.ts", "t1") | ||
| reg.observe("/b.ts", "t2") | ||
| expect(reg.size).toBe(2) | ||
| }) | ||
|
|
||
| it("clear removes all entries and resets size to 0", () => { | ||
| const reg = new ObservationRegistry() | ||
| reg.observe("/a.ts", "t1") | ||
| reg.observe("/b.ts", "t2") | ||
| reg.clear() | ||
| expect(reg.size).toBe(0) | ||
| expect(reg.get("/a.ts")).toBeUndefined() | ||
| expect(reg.has("/b.ts")).toBe(false) | ||
| }) | ||
|
|
||
| it("get on empty registry returns undefined", () => { | ||
| const reg = new ObservationRegistry() | ||
| expect(reg.get("/any.ts")).toBeUndefined() | ||
| }) | ||
|
|
||
| it("separate instances are independent — observing in one does not appear in the other", () => { | ||
| const regA = new ObservationRegistry() | ||
| const regB = new ObservationRegistry() | ||
| regA.observe("/shared.ts", "v1") | ||
| expect(regA.get("/shared.ts")).toBeDefined() | ||
| expect(regB.get("/shared.ts")).toBeUndefined() | ||
| regB.observe("/shared.ts", "v2") | ||
| expect(regA.get("/shared.ts")!.version).toBe("v1") | ||
| expect(regB.get("/shared.ts")!.version).toBe("v2") | ||
| }) | ||
|
|
||
| describe("completeness scope (S4b follow-up #46)", () => { | ||
| it("defaults to a complete observation when the read scope is not given", () => { | ||
| const reg = new ObservationRegistry() | ||
| reg.observe("/a/b/c.ts", "v1") | ||
|
|
||
| expect(reg.get("/a/b/c.ts")!.complete).toBe(true) | ||
| }) | ||
|
|
||
| it("records a partial observation when the read only returned a view of the file", () => { | ||
| const reg = new ObservationRegistry() | ||
| reg.observe("/a/b/c.ts", "v1", false) | ||
|
|
||
| expect(reg.get("/a/b/c.ts")!.complete).toBe(false) | ||
| }) | ||
|
|
||
| it("re-observing replaces the entry's completeness with the new read's scope", () => { | ||
| const reg = new ObservationRegistry() | ||
| reg.observe("/a/b/c.ts", "v1", false) | ||
| reg.observe("/a/b/c.ts", "v2") | ||
|
|
||
| const obs = reg.get("/a/b/c.ts")! | ||
| expect(obs.version).toBe("v2") | ||
| expect(obs.complete).toBe(true) | ||
| }) | ||
|
|
||
| it("re-observing with a partial scope downgrades a previously complete entry", () => { | ||
| const reg = new ObservationRegistry() | ||
| reg.observe("/a/b/c.ts", "v1") | ||
| reg.observe("/a/b/c.ts", "v2", false) | ||
|
|
||
| const obs = reg.get("/a/b/c.ts")! | ||
| expect(obs.version).toBe("v2") | ||
| expect(obs.complete).toBe(false) | ||
| }) | ||
| }) | ||
|
|
||
| describe("closure on task disposal", () => { | ||
| it("close drops every entry and reports the registry closed", () => { | ||
| const reg = new ObservationRegistry() | ||
| reg.observe("/a/b/c.ts", "v1") | ||
| expect(reg.closed).toBe(false) | ||
|
|
||
| reg.close() | ||
|
|
||
| expect(reg.size).toBe(0) | ||
| expect(reg.get("/a/b/c.ts")).toBeUndefined() | ||
| expect(reg.closed).toBe(true) | ||
| }) | ||
|
|
||
| it("observe is a no-op after close, so a read resuming after disposal records nothing", () => { | ||
| const reg = new ObservationRegistry() | ||
| reg.close() | ||
|
|
||
| reg.observe("/late.ts", "v1") | ||
|
|
||
| expect(reg.size).toBe(0) | ||
| expect(reg.has("/late.ts")).toBe(false) | ||
| }) | ||
| }) | ||
| }) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Script executed:
Repository: Zoo-Code-Org/Zoo-Code
Length of output: 27076
Script executed:
Repository: Zoo-Code-Org/Zoo-Code
Length of output: 25761
Script executed:
Repository: Zoo-Code-Org/Zoo-Code
Length of output: 22796
Script executed:
Repository: Zoo-Code-Org/Zoo-Code
Length of output: 21855
Script executed:
Repository: Zoo-Code-Org/Zoo-Code
Length of output: 11821
Script executed:
Repository: Zoo-Code-Org/Zoo-Code
Length of output: 2129
Web query:
Node.js fs promises writeFile default flag w replaces existing file contents official documentationResult:
Enforce observations in the guarded-write follow-up.
With
preventFocusDisruptionenabled, an approved full-file write can overwrite unseen or newer content.write_to_filedoes not check the recorded completeness or version beforesaveDirectlyreplaces the file. Connect both checks to this publishing path in the follow-up.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vps2-f triage. Not #2357's content: git diff 416179d 8a9d5b7 is empty for this file, so the line arrived from #1913's head and rides here only because main does not contain 416179d yet. The row's home is #1913. Line 302, the readonly observationRegistry field, is not in this unit's added lines either: the Task.ts additions here are 401-405, 3604-3608, 3619-3622 and 3651. The claim about the publishing path is accurate for the pre-existing write_to_file and saveDirectly route. It is not true of the tool this stack is shipping: guardedWrite, on #2359's head, consults task.observationRegistry at guardedWrite.ts:573, requires a complete observation for a full-file publish, rejects stale when the on-disk token differs, and refreshes the observation after publish at 657 and 679. What remains open is migrating the legacy tool onto that path, which the split plan assigns to neither #2357 nor #2359. I am not changing code in this pass and I am not resolving the thread.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Analysis chain
Script executed:
Length of output: 14487
@easonLiangWorldedtechI agree this request is outside#2357’s unit scope. I withdraw my request for a change in this PR.The comparison from
416179dto8a9d5b7changesTask.ts, but it does not addobservationRegistry. It addscancellationGeneration. The legacyWriteToFileTool.tsandDiffViewProvider.tsfiles are unchanged in that comparison.On
#2359’s checked head,guardedWriterequires a complete observation for an existing full-file replacement and passes the observed version toreplaceIfVersion. It does not migratewrite_to_file.The thread will remain open, as requested. Would you like a follow-up issue for the legacy tool migration?
Learnings used
You are interacting with an AI system.