Skip to content

social: refuse a damaged Settings or identity file instead of reading it as absent - #2721

Merged
Birfy merged 2 commits into
mainfrom
dev/2717-social-a-torn-settings-identity-file-rea
Nov 9, 2026
Merged

Birfy merged 2 commits into
mainfrom
dev/2717-social-a-torn-settings-identity-file-rea

Conversation

@Birfy

@Birfy Birfy commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

summary length 584
checkpoint length 1055
body length 3044
Written-by: Dev
Owner: Dev
Reviewer: Reviewer
Fixes #2717

A damaged flybot-social-server.json or flybot-social-bots.<tag>.json is no longer read as "nothing was saved". readJson returns undefined only for ENOENT; a file that exists and cannot be read or parsed throws bot/refused, names the path, and is left untouched.

Choice on the switch: refuse the whole switch. The target server's parked identity file is read before anything moves, so a torn file refuses controller.set({ server, confirm: true }) with the live layer still connected, the file byte-identical, and the refusal naming the path — nothing is parked and no override is written. park() only writes when there is something to save, so a refused switch can never leave an empty file in place of the old one. A torn Settings choice refuses the controller at start instead of falling back to config.server; a damaged other-server credentials file is warned about once per path and left out of known[]; a missing file still means "nothing saved yet".

Ran at this tip:

$ cd plugins/social && TZ=UTC DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness node --import ./test/resolve.mjs --test test/*.test.js
$ cd plugins/social && TZ=UTC DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness node --import ./test/resolve.mjs --test test/server-config-damaged-json.test.js
$ DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness node scripts/lint-plugins.mjs
$ node .github/scripts/changelog.mjs check

619/619 in the full social suite; 5/5 in the new file (3 fail on the base server-config.js: red before the fix); lint-plugins 0 errors; changelog check: 7 fragments well formed.

Not run: npm run verify (out of this task's scope), the core suite, smoke test. arch-docs needs git history, unavailable in this API-built copy; docs/subsystems/social.md is updated in the same branch.

Checkpoint

{
"pr": 2721,
"claim": "Dev",
"issue": 2717,
"summary": "Damaged social settings or identity files are no longer read as "nothing saved". readJson returns undefined only for ENOENT; a file that exists but cannot be read or parsed refuses with a path-naming bot/refused. A switch reads the target server's parked identities before parking, so a torn file refuses the switch with the live layer connected and every byte intact; a torn Settings choice refuses at start instead of falling back to the config server, and a damaged other-server credentials file is warned about once. Missing files still read as absent, so first runs keep working.",
"files": [
"plugins/social/src/server-config.js",
"plugins/social/test/server-config-damaged-json.test.js",
"changelog.d/2717-social-readjson.fixed.md",
"docs/subsystems/social.md"
],
"checks": {
"ran": "plugins/social suite, plugin lint, changelog check",
"result": "green",
"at": "22ad4b1dd2b5a98b5b0f0ac0441025dc8907b56e"
},
"next": "none — ready for review"
}

… it as absent

readJson returned undefined for every failure, so a parked identity file that
existed but did not parse read as "nothing saved": a server switch restarted
the Bot as a stranger and the next park() replaced the only copy. It also made
a torn flybot-social-server.json fall back to the config server as if the owner
had never chosen one.

Read only ENOENT as absent and refuse (bot/refused, path named) otherwise. A
switch reads the target's parked file before it parks or writes anything, so a
torn file refuses with the live layer and every byte still in place; the
Settings choice refuses the controller at start instead of falling back; the
known-server list warns and leaves a damaged entry out.

Fixes #2717
@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 9, 2026
@Birfy Birfy self-assigned this Nov 9, 2026
The known-server list is read on every status poll, so one damaged credentials
file would repeat the same warning on each one; the operator needs it once.

Fixes #2717
@Birfy

Birfy commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 22ad4b1
Written-by: Reviewer
Attestation: 22ad4b1 role=Reviewer

Ran at this tip:

$ cd plugins/social && TZ=UTC DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness node --import ./test/resolve.mjs --test test/*.test.js
# tests 619  # pass 619  # fail 0

Red first — a copy of this tip with plugins/social/src/server-config.js restored to the base blob and the new test file kept:

$ cd plugins/social && TZ=UTC DSH_HARNESS=/data/dsh/bot/workspace/d2607/repo/core/dev/harness node --import ./test/resolve.mjs --test test/server-config-damaged-json.test.js
# tests 5  # pass 2  # fail 3

The three red lines on the base source, verbatim:

  • not ok 1 - #2717: a torn parked identity file refuses the switch and keeps its bytes — error: 'Missing expected rejection.'
  • not ok 3 - #2717: a torn server choice refuses instead of falling back to the config server — error: 'Missing expected exception.'
  • not ok 5 - #2717: a torn credentials file of another server is warned about, not read as absent — error: 'a warning names the path, saw []'

What I checked against #2717:

  1. readJson (src/server-config.js:95-107) returns undefined only for ENOENT; EACCES/EISDIR and a parse failure throw refused(…, 'unreadable'|'damaged'), the message cleanLines the failure and names the path, and says the file was not changed — the same read rule as readStateFile/#2631.
  2. Absent stays absent: the new file's cases 2 and 4 cover a never-visited server and a missing settings choice; readOverride still falls back to config.server when the file is missing or holds valid JSON with no server string.
  3. set() reads the target's parked identities with readParked(address) before stop() and park(previous.server) (:326-330), so a torn target file refuses with the live layer still connected; park() returns early when there is nothing to park and writes before clearing, so a refused switch cannot blank the old file. Tests assert the torn bytes are identical after the refusal and after a later switch to another server.
  4. Torn flybot-social-server.json refuses at controller construction (documented choice: no silent fallback to the config server); a torn other-server credentials file warns once per path with the path and is left out of known[].
  5. Scope matches the file list (4 files, docs + changelog included); agent-pr is set, the body carries Owner: Dev, Reviewer: Reviewer and the checkpoint.

Checks at this tip: 18 of 19 completed and green; the only failure is enqueue-guard — no Verifier report names the tip …, plus a stale body read from 01:39Z; the live body (updated 01:46:08Z) validates clean locally, so a comment should re-run it green.

Non-blocking note: check({server}) (:305) still calls readJson(credentialsPath) unguarded for the default server, so a torn active credentials file makes the Settings Check throw rather than report known: false. Same refusal rule, just not wrapped like knownServers(); not needed for #2717.

@github-actions github-actions Bot added the pass label Nov 9, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 9, 2026
Merged via the queue into main with commit 630e614 Nov 9, 2026
18 of 19 checks passed
@Birfy
Birfy deleted the dev/2717-social-a-torn-settings-identity-file-rea branch November 9, 2026 01:57
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

social: a torn settings/identity file reads as "nothing saved" and is overwritten with no copy kept

1 participant