Skip to content

fix(portable): fail closed on Agent Plugins normative MCP rules in ordinary builds (#307 review follow-up) - #406

Merged
ScriptedAlchemy merged 7 commits into
mainfrom
fix/307-review-followups
Sep 3, 2026
Merged

fix(portable): fail closed on Agent Plugins normative MCP rules in ordinary builds (#307 review follow-up)#406
ScriptedAlchemy merged 7 commits into
mainfrom
fix/307-review-followups

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Summary

Follow-up to #373 addressing both Codex review threads that landed after merge (per the maintainer's review-thread rule).

  • P1 — Run the portable byte lane during ordinary builds: the Agent Plugins normative rules (§7.2.1 command/cwd/URL/header forms, §9.2 env-key placeholders) were only enforced under validate --artifact --host-validation. They are now extracted into packages/agent-bundle/src/adapters/portable-mcp-rules.ts and applied twice: at plan time inside portable.ts (portable.mcp.{command,cwd,env,url,headers}.standard, errors — a standard-invalid server never reaches an artifact) and as the full byte lane (AB6035AB6037) over every emitted portable/ tree inside ordinary validateArtifact, which both build and validate --artifact run. command: "bin/server", an escaping cwd, duplicate case-insensitive headers, and plain HTTP off loopback now fail the ordinary build.
  • P2 — Reject all forbidden HTTP header control characters: header values now reject every character outside VCHAR / SP / HTAB / obs-text (RFC 9110 §5.5; mirrors Node's validateHeaderValue), not only CR/LF/NUL.
  • The mcp-session-service remote fixture carried Authorization: 'Bearer ${PLUGIN_DATA}', which the standard forbids in headers and the byte lane already rejected; the fixture now uses a literal token and asserts pass-through.
  • Docs (docs/diagnostics.md, docs/framework-mode.md, README portable section) and a changeset with an explicit backward-compatibility note.

Evidence

  • Plan-time gate: packages/agent-bundle/tests/portable-adapter.test.ts — "fails closed at plan time on Agent Plugins 1.0.0 normative MCP rules the schema cannot express" (7 diagnostics, one per rule; conformant sibling server still emitted).
  • Ordinary artifact gate: packages/agent-bundle/tests/artifact-validator.test.ts — "fails ordinary artifact validation when an emitted portable tree breaks the Agent Plugins normative text" (validateArtifact without host validation returns four AB6036 errors; no AB6012).
  • Header control characters: packages/agent-bundle/tests/portable-plugin-validation.test.ts — "rejects every forbidden control character in HTTP header values while permitting horizontal tab" (SOH, BEL, LF, VT, CR, DEL, NUL, U+2014 rejected; HTAB, obs-text, visible ASCII accepted).

Test plan

  • pnpm typecheck — exit 0
  • pnpm lint — 0 errors, 0 warnings
  • pnpm test:unit — 2704 passed; 7 failures were load-induced timeouts (event-ipc, inspect-state, mcp-probe-service, native-claude-contract, rsc-runtime dispatcher) that fail identically on a clean origin/main baseline at load average ~128
  • pnpm test:projection — 63 passed
  • pnpm test:route-unit — 36 passed
  • pnpm build && pnpm test:integration:run — 945 passed; mcp-session-service fixture fixed (above), dev-artifact-service 30 s timeout re-ran green in isolation

@changeset-bot

changeset-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0b15c5c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
agent-bundle Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-03T09:56:11.403082Z 33e5b0c Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@pkg-pr-new

pkg-pr-new Bot commented Sep 3, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle@406
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/create-agent-bundle@406
npm i https://pkg.pr.new/ScriptedAlchemy/agent-bundle/@agent-bundle/runtime@406

commit: 0b15c5c

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d51bb3335

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}): Promise<readonly Diagnostic[]> => {
const diagnostics: Diagnostic[] = [];
for (const target of options.manifest.targets) {
if (target.name !== portableAdapter.name) continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit the byte lane to the built-in portable adapter

A caller can legally supply an advanced TargetRegistry containing a different adapter named portable, but this name-only check unconditionally validates that adapter's output as Agent Plugins 1.0.0. Such an adapter may declare and emit its own required document through artifactValidation; ordinary build or validate --artifact will now additionally report AB6035 because plugin.json is absent or unrelated. Tie this lane to the registered built-in adapter/contract rather than reserving the name implicitly.

Useful? React with 👍 / 👎.

@ScriptedAlchemy ScriptedAlchemy Sep 3, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — fixed in 866e7c5. validatePortableTargets now runs only when registry.get(target.name) === portableAdapter (the registry stores and returns the registered adapter object, so this is an exact identity check), instead of matching the portable name. Test: artifact-validator.test.ts "leaves an advanced registry adapter that reuses the portable name to its own artifact contract" — a custom adapter registered as portable with its own document.json contract yields no AB6035AB6037; only the pre-existing name-keyed install-surface requirement (AB6023/AB6024, out of scope here) remains.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 866e7c585a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +75 to +76
const candidate = resolve(anchor, normalize(relativePart === '.' ? './' : relativePart));
if (isInsideOrEqual(anchor, candidate)) return Object.freeze([]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use platform-independent cwd containment

When a portable artifact is built on POSIX and consumed on Windows, a value such as ./safe\..\..\outside passes this check because POSIX normalize treats backslashes as ordinary characters, but Windows resolves the same value outside the plugin root. Since the target is portable and the emitted cwd is interpreted by the consuming host, reject backslashes/NULs or normalize both separator forms rather than using the build host's node:path implementation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — fixed in 22a388b. portable-mcp-rules.ts no longer uses the build host's node:path: ./ commands and cwd values containing a backslash or NUL are refused outright (new cwd message: "must use forward-slash separators without backslashes or NUL so every consuming platform resolves it identically (§4.1)"), containment is computed with node:path/posix only, and absolute-command detection checks both posix.isAbsolute and win32.isAbsolute so C:\\tools\\server.exe is rejected on a POSIX build host too. Tests: portable-adapter.test.ts (plan time: ./safe\\..\\..\\outside cwd, ./bin\\..\\..\\outside command, Windows-absolute command) and portable-plugin-validation.test.ts (byte lane: same backslash cwd/command cases).

"agent-bundle": minor
---

portable: fail closed on the Agent Plugins 1.0.0 normative MCP rules during ordinary builds (#307 review follow-up) — the command-form, cwd-containment, env-key-placeholder, URL-form, and header rules the pinned `mcp.schema.json` cannot express now run at plan time (`portable.mcp.{command,cwd,env,url,headers}.standard`, errors) and the Agent Plugins byte lane (`AB6035`–`AB6037`) runs over every emitted `portable/` tree in `build` and `validate --artifact`, so a standard-invalid `mcp.json` (for example `command: "bin/server"`, an escaping `cwd`, duplicate case-insensitive headers, or plain HTTP off loopback) can no longer be published unless `--host-validation` is requested. Header values are now rejected when they contain any character outside visible ASCII, space, horizontal tab, and obs-text bytes (RFC 9110 §5.5; matches Node's `validateHeaderValue`), not only CR/LF/NUL. Backward compatibility: portable bundles whose MCP servers already satisfy the standard emit byte-identical output; servers that relied on path-bearing or whitespace-bearing `command` values, escaping `cwd`, or plain-HTTP remote URLs now fail the build with a field-scoped diagnostic instead of being published.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Correct the reversed host-validation release note

This says a standard-invalid artifact can still be published when --host-validation is requested, but the new byte lane runs during every ordinary build and the flag only adds the host report/provenance note. The exception reverses the actual behavior and should say that publication is blocked even when host validation was not requested.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — fixed in 22a388b. The changeset now reads "can no longer be published, whether or not --host-validation is requested (the flag now only adds the portable host report and its AB6038 provenance note)", matching the behaviour proven by artifact-validator.test.ts "fails ordinary artifact validation when an emitted portable tree breaks the Agent Plugins normative text" (validateArtifact with no host validation returns the AB6036 errors).

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22a388bad5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

]);
diagnostics.push(
...targetContractDiagnostics,
...portableTargetDiagnostics,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid duplicating portable diagnostics during host validation

When validate --artifact --host-validation processes an invalid portable artifact, this newly adds the AB6035AB6037 byte-lane results to validated.diagnostics, after which api.ts invokes validatePortablePlugin and flattens the same byte-lane results from the host report into the top-level diagnostics. Consequently, each portable violation is reported twice—once with the Target "portable": prefix and once without it. Reuse the ordinary lane for the portable report or exclude its already-collected diagnostics when aggregating host-validation results.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a defect as described, but pinned with a test in 5d02c02. In api.ts the host reports run only on the validated.snapshot !== undefined branch, and validateArtifactWithSnapshot returns a snapshot only when the ordinary lane produced zero diagnostics (invalidArtifactSnapshot omits it). So when the byte lane fails, validate --artifact --host-validation returns early with the single Target "portable": … copy of each AB6035AB6037 and never calls validatePortablePlugin; when it passes, the report re-runs a lane that finds nothing and contributes only the AB6038 provenance info. New assertion in artifact-validator.test.ts "fails ordinary artifact validation when an emitted portable tree breaks the Agent Plugins normative text": validate({ artifact, hostValidation: true }) yields exactly the same four AB6036 messages as validateArtifact, hostValidation is undefined, and no AB6038 appears.

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

1 similar comment
@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22a388bad5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +46 to +49
const staysInsidePosixRoot = (relativePath: string): boolean => {
const root = '/plugin/anchor';
const resolved = posix.normalize(posix.join(root, relativePath));
return resolved === root || resolved.startsWith(`${root}/`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject paths that collide with the synthetic anchor

When a command or cwd climbs out and then names the literal synthetic directory, such as ./../anchor/server or ${PLUGIN_ROOT}/../anchor, normalization lands back under /plugin/anchor and this function incorrectly returns true. The emitted plugin root is generally not named anchor, so the consuming host resolves these paths outside the plugin; moreover, stdioDiagnostics silently skips its filesystem check when pluginRelativeTarget() returns undefined, allowing the escaping command through ordinary build and artifact validation. Check the normalized relative path for a leading .. instead of resolving against a collidable fixed basename.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — real containment bug, fixed in 0ec9194. containedPortableRelativePath now normalizes the relative value with node:path/posix and rejects it when the normalized path itself is .. or starts with ../ (or is absolute / carries backslash or NUL); there is no synthetic anchor to collide with any more. The byte lane's pluginRelativeTarget uses the same helper, so the ./ bundled-file check can no longer be skipped for a value the rule accepted. Tests: portable-adapter.test.ts (plan time: ./../anchor/server command and ${PLUGIN_ROOT}/../anchor cwd both rejected) and portable-plugin-validation.test.ts (byte lane: anchorCommand/anchorCwd rejected).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d02c027f4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +457 to +460
for (const entry of await validatePortablePluginFiles({
pluginDirectory: resolve(options.artifactRoot, target.name),
target: target.name,
})) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject symlinks before running the portable byte lane

When validate --artifact processes a tampered portable tree containing an escaping symlink at plugin.json, mcp.json, or skills, the initial filesystem inspection records AB6013, but this call still runs. validatePortablePluginFiles uses stat followed by readFile/readdir, which follows those symlinks concurrently with the later containment check, so validation can read a very large or blocking file or enumerate a directory outside the artifact before reporting the symlink. Use lstat/realpath containment before reading these paths, or skip this lane for already-unsupported filesystem entries.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — fixed in 0ec9194. validatePortableTargets now takes the inspection's filesystem snapshot and skips the byte lane for any portable tree that already holds a symlink/other entry (the same predicate that produces AB6013), so plugin.json, mcp.json and skills are never stated, read or enumerated once their containment has been refused. Test: artifact-validator.test.ts "does not follow a symlinked portable document into the byte lane once the inspection refused it" — portable/mcp.json symlinked to an outside file containing a standard-invalid server yields AB6013 only, with no AB6035AB6037 from behind the link (before the fix the lane read it and reported AB6036).

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ec9194c23

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
return Object.freeze([]);
}
if (command.length === 0 || /[\s/\\\0]/u.test(command) || isAnyPlatformAbsolute(command) || command.startsWith('.')) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject drive-relative Windows commands

For an input such as command: "C:server", win32.isAbsolute() returns false and none of the other predicates reject the drive prefix, so both plan-time and byte-level validation accept it. On a Windows consumer this is a drive-relative path—not a bare executable name—and resolves against drive C's per-drive working directory, potentially outside the plugin root. Reject drive-qualified relative forms as well, for example by checking whether win32.parse(command).root is nonempty.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — fixed in 33e5b0c. Bare-command detection now treats any nonempty win32.parse(command).root as rooted (alongside posix.isAbsolute/win32.isAbsolute), so C:server is rejected as neither a bare executable name nor a plugin-relative ./ path, at plan time and in the byte lane. Tests: portable-adapter.test.ts (windows-drive-relative) and portable-plugin-validation.test.ts (driveRelativeCommand).

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 33e5b0cdfe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…dinary builds (#307 review follow-up)

Address the two Codex review threads on #373:

- P1: the normative byte lane only ran under `--host-validation`, so `build`
  and `validate --artifact` still published standard-invalid portable output.
  Extract the pure §7.2.1/§9.2 rules into `adapters/portable-mcp-rules.ts`,
  apply them at plan time (`portable.mcp.{command,cwd,env,url,headers}.standard`),
  and run the full byte lane (AB6035–AB6037) over every emitted `portable/`
  tree inside ordinary artifact validation.
- P2: header values only rejected CR/LF/NUL; now reject every character
  outside VCHAR / SP / HTAB / obs-text (RFC 9110 §5.5, matching Node's
  validateHeaderValue).

The mcp-session-service remote fixture carried a header placeholder the
standard forbids; it now uses a literal token and asserts pass-through.
…P fixture

The packed consumer fixture wrote command: process.execPath into portable
mcp.json; Agent Plugins §7.2.1 forbids absolute command paths and the build
now fails closed on it (AB6036). Match the compiler's bare 'node' emission.
…apter identity, not the target name

An advanced TargetRegistry may bind 'portable' to its own adapter and
artifactValidation contract; that output is not Agent Plugins 1.0.0 and must
not be reported as AB6035. Compare registry.get(name) against portableAdapter.
…correct the release note

Refuse backslashes and NUL in ./ commands and cwd values and normalize with
POSIX semantics only, so a bundle built on POSIX cannot resolve outside the
plugin root on Windows. The changeset wrongly said publication was blocked
only without --host-validation; the byte lane runs in every ordinary build.
… portable byte lane

Host reports only run over an artifact the ordinary lane accepted, so an
AB6035-AB6037 failure is reported once and no portable report is produced.
…skip the byte lane over refused filesystem entries

- ./../anchor/server and ${PLUGIN_ROOT}/../anchor collided with the synthetic
  anchor and passed; containment now rejects a normalized path that starts
  with '..' (shared by planner and byte lane).
- validate-artifact skips the byte lane for a portable tree whose inspection
  already recorded a symlink or other unsupported entry (AB6013), so tampered
  documents are never stat'ed or read before their containment is reported.
win32.isAbsolute reports them relative, but they resolve against a per-drive
working directory on a Windows consumer; treat any nonempty win32 root as
rooted.
@ScriptedAlchemy
ScriptedAlchemy force-pushed the fix/307-review-followups branch from 33e5b0c to 0b15c5c Compare September 3, 2026 10:21
@ScriptedAlchemy
ScriptedAlchemy merged commit 98751df into main Sep 3, 2026
11 checks passed
@ScriptedAlchemy
ScriptedAlchemy deleted the fix/307-review-followups branch September 3, 2026 10:49
ScriptedAlchemy added a commit that referenced this pull request Sep 3, 2026
ScriptedAlchemy added a commit that referenced this pull request Sep 3, 2026
ScriptedAlchemy added a commit that referenced this pull request Sep 3, 2026
ScriptedAlchemy added a commit that referenced this pull request Sep 3, 2026
ScriptedAlchemy added a commit that referenced this pull request Sep 3, 2026
/#379/#385/#390) (#408)

* fix: address late review threads on merged PRs #368/#373/#374/#377/#378/#379/#385/#390

- dev: lease the adopted epoch in EpochAdoptionPolicy until replaced or closed;
  select the contract-matrix target from the server's own target list; apply the
  session timeout per matrix request; observe lifecycle progress through the
  session trace via a new ContractMatrixClient.observeProgress seam (#385)
- playground: wait for a hard-link catalog publisher to release its staging link
  before adopting the sidecar; return to discovery when the publication is
  rolled back (#377)
- build: run the Agent Plugins byte lane over portable/ during ordinary artifact
  validation; reject every forbidden control character in header values (#373)
- events/hooks: Codex PostToolUse accepts any present JSON tool_response (#378)
- api: project only contract fields of adapter capability rows in inspect (#390)
- tests/support: digest the real Claude home in the live session guard; isolate
  USERPROFILE alongside HOME (#374)
- docs: Claude plugin-root cwd exception, parked-pin trigger independence,
  provider typing contract, portable validation moments (#368/#379/#382/#373)

* fix(dev): lease before publishing contract status; align fixtures with the portable byte lane

- EpochAdoptionPolicy acquires the epoch lease before publishing a passed
  dev.contract.status and announces adoption synchronously with it, so a
  status reader never sees "passed" for an epoch that is not yet adopted
- hooks.test: Codex PostToolUse accepts a string tool_response and rejects a
  missing one; Claude keeps the object check
- mcp-session-service/public-api-packed fixtures: Agent Plugins forbids
  placeholders in headers and non-bare/non-./ commands, and ordinary artifact
  validation now enforces the standard, so the fixtures carry a literal header
  and a bare `node` command

* chore(changeset): drop the Codex tool_response bullet already released by #404

* fix(playground,test): withdraw a failed catalog publication before releasing its staging link; bind custom observeProgress

- #persistSnapshot rolls the sidecar back while the staging link still exists
  when the post-link directory fsync fails, so a concurrent reader keeps seeing
  an in-progress publication until the path is withdrawn instead of adopting a
  briefly singly linked file
- contractProgressObserver invokes a client's observeProgress method with the
  client as receiver

* chore(changeset): one-paragraph summary ending with the PR reference

* fix(playground): recover a catalog staging link abandoned by an exited publisher

After the settle deadline, a matching .stage-<pid>-* link whose publisher pid
no longer exists is an abandoned publication of an already fsynced sidecar:
withdraw the orphan and adopt the sidecar instead of rejecting the epoch
forever. A live publisher's staging link is still never yanked. Adds the
@internal catalogStagingSettleDeadlineMs seam for deterministic tests.

* fix(playground): fsync the catalog directory after withdrawing an abandoned staging link

* fix(dev): forward request _meta (progress token) through McpSession and the dev matrix client

Lifecycle fixtures pass their generated progressToken as params._meta; the
session adapter and McpSession.callTool dropped it, so generated routes never
enabled sendProgress and every progress-gated lifecycle fixture failed the dev
matrix. McpSessionToolCallOptions and McpClient.callTool now carry _meta.

* fix(dev,playground): restart the adoption drain after a handoff race; keep the staging link when a sidecar rollback fails

- EpochAdoptionPolicy reschedules its drain from the completion handler when a
  candidate arrived between the loop's last empty check and #processing being
  cleared, and settled() waits through restarts
- #persistSnapshot releases the staging link after a failed publication only
  once the owned sidecar is confirmed withdrawn, so a rollback failure never
  leaves a singly linked sidecar for readers to adopt

* ci: retrigger checks for the rebased head

* chore: drop the portable byte-lane changes superseded by #406; keep the _meta assertions

* fix(playground): restore the staging guard when a recovery fsync fails

* fix(playground): accept a concurrently restored staging guard (EEXIST aliasing the sidecar) during recovery

* fix(dev): recheck supersession after the adoption lease settles

* fix(playground): keep a fresh pid-owned guard when recovery can neither re-link nor withdraw the sidecar

* fix(playground): fsync every compensating recovery guard before trusting it
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant