Conversation
This was referenced Aug 25, 2026
XieX
force-pushed
the
xie/skills-07-docs
branch
from
August 28, 2026 18:02
5a94554 to
f4a0ad6
Compare
XieX
force-pushed
the
xie/skills-07-docs
branch
from
August 28, 2026 20:30
f4a0ad6 to
f1dcb1b
Compare
XieX
force-pushed
the
xie/skills-07-docs
branch
from
August 30, 2026 08:29
f1dcb1b to
f4bd7d1
Compare
XieX
force-pushed
the
xie/skills-07-docs
branch
from
August 31, 2026 18:10
f4bd7d1 to
c7fd991
Compare
XieX
force-pushed
the
xie/skills-07-docs
branch
from
September 14, 2026 20:13
c7fd991 to
defbc1a
Compare
XieX
marked this pull request as ready for review
September 14, 2026 20:35
XieX
force-pushed
the
xie/skills-07-docs
branch
from
September 16, 2026 18:26
defbc1a to
50721ca
Compare
andrewklatzke
approved these changes
Sep 16, 2026
Base automatically changed from
xie/skills-06-write-fs-tests
to
xie/agent-skills-feature-ac9ac7
September 18, 2026 19:19
Documents the feature now that all of it exists. The README section and the contributor notes both span every layer in the stack, so they land once rather than as six partial edits to the same two files. `README.md` gains the public API — the accessors, `writeSkills`, the store seam, the exported constants, and the types — and states two things plainly rather than leaving a reader to discover them. Content delivery is not wired up in this release: everything runs against `SkillStore`, the shipped default is absent, and the accessors throw an actionable error until one is configured. And the security posture, including the limitation: Node exposes no `renameat`/`unlinkat`, so the final rename cannot be performed relative to a pinned directory descriptor, and an attacker who already has write permission on the managed root can still win a race to redirect a write or a delete outside it. Keep the managed root writable only by the process running the SDK. Two behaviours a reader would otherwise be surprised by are stated where the conservatism itself is described. First, the one narrow exception to "a file you placed yourself is never overwritten": a colliding file whose bytes *already are* the resolved content is adopted rather than refused, which is what lets a reconcile killed between the content writes and the final manifest write heal itself instead of wedging those skills forever. Nothing is overwritten, because nothing needs to be. Differing bytes, and bytes that cannot be read at all, are still refused and left untouched. Second, the two key constraints this layer imposes that the data model does not — the 255-byte path-component bound and the 22 Windows reserved device names — including why a key like `aux` still parses everywhere else, and the residual the SDK cannot check: the bound is per component, so a deep root plus a long key can still exceed Windows' `MAX_PATH`. `agents.md` records what a future change has to preserve: the module ownership rows, the one-way dependency direction (`types` <- `skills-core` <- `skills`, and `types` + `safe-fs` + `skills-core` <- `skills-fs`) and why closing a cycle there would let the accessor and filesystem layers disagree about whether a store is configured, and a Dependencies table giving a reason for every runtime dependency and optional peer. Skill content is documented as what it now is: `Skill.content` is a `Uint8Array` of the verified verbatim bytes, opaque to the SDK — no frontmatter accessor, no YAML dependency, and consumers who want structure parse the bytes themselves. Four new pitfalls, each one a mistake that would pass review: interpreting skill content anywhere in the SDK, reading `writeSkills`'s `timeout` as milliseconds, relaxing a path or manifest check in `skills-fs.ts`, and bypassing `fsOps` for a destructive call — which makes the operation invisible to the assertions that prove no operation was attempted, so they then pass vacuously. The path-check pitfall carries the three specifics most likely to be widened by someone who has not read the reasoning: why the reserved-name rejection is unconditional rather than platform-gated and why it lives in `keyRejectionReason` rather than in the key grammar (a grammar-level rejection would invalidate the whole AI Config, and a dropped `skillRefs` entry lets prune delete the on-disk copy — "fails to write on Windows" becoming "gets deleted on Linux"); that adoption is a narrowing of the clobber refusal and requires an exact hash match; and that every flag in `readRegularFile` is load-bearing, `O_NONBLOCK` most of all, since opening a FIFO with no writer blocks forever. Also renames one pre-existing test to describe its behaviour directly instead of citing a specification section. Workspace 1148 passing (+2 skipped). typecheck, biome, and sherif clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
XieX
added a commit
that referenced
this pull request
Sep 18, 2026
> **Stacked PR 6 of 7 — merge bottom-up into `xie/agent-skills-feature-ac9ac7`.** > 1. #29 — value types > 2. #30 — symlink-refusing filesystem primitives > 3. #31 — store seam, verification, accessors > 4. #32 — integrity + telemetry tests > 5. #33 — writeSkills + abuse matrix > 6. **#34 — remaining writeSkills tests ← you are here** > 7. #35 — docs > > Each PR targets the one below it, so GitHub already shows only this PR's own diff. --- Sixth of seven. Test-only — the functional half of the materialization suite, split out so it isn't read alongside the implementation and the abuse matrix in one sitting. This closes the coverage gap the previous PR flagged: until now `writeSkills` had its defenses proven but not its ordinary behaviour. ## What's covered - **Basic writes and the returned report** — what lands on disk, at what path, and what each `ReconcileAction` says about it. - **Manifest** — the serialization is asserted byte for byte, since it's a cross-language on-disk contract. Written literally rather than by importing the implementation's own constants: a test that imported them could not detect a change to them. - **Reconcile semantics** — `written`, `updated`, `skipped_current`, `removed`, and the `prune` and `onUnavailable` behaviours, including that a retrieval failure under `keep` leaves existing content alone. - **Root handling**, and the bare-string guard on the argument surface. - **Atomicity and permissions** — the temp file is created in the target's own directory so the rename is same-filesystem rather than cross-device, the mode is `0644`, and a failure injected between write and rename leaves no partial file and produces an `error` action. - **Resilience** — an unreadable directory, a failing unlink, a manifest that cannot be rewritten. One skill failing never takes the run down, and every failure appears in the report. - **Verify-then-write** — content is re-verified immediately before the write, so a `Skill` a caller built by hand gets the same treatment as one an accessor produced. Directly constructed `Skill`s carry `Uint8Array` content, and the pass hashes those bytes as-is; the `content_bytes` telemetry property is asserted to be the encoded byte count. - **Orphaned temp files** — the sweep added in #33 removes an orphan a killed reconcile left behind, and does so ahead of the prune, so the `rmdir` that empties a skill directory is no longer blocked by one. Most of the block is about what the sweep must *not* touch: a customer file, a temp-shaped name that fails the anchored pattern at either end, a temp for another target, a symlink wearing a temp name, anything in the managed root itself, and everything at all when the manifest is corrupt. - **The write half of the telemetry seam**, with the same allowlist sweep the accessor half applies: three signal names, no content, no paths. ## Verification Client package 555 → 619 tests (+2 skipped). Workspace 1148 passing. `typecheck`, `biome`, and `sherif` clean. The 2 skips are expected: the TOCTOU swap-race pair, gated off `SUPPORTS_DIR_FD`, which is `false` on Node because it exposes no `*at()` syscall family. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > **Test-only expansion** of `skills-fs.test.ts`: closes the gap where `writeSkills` had security/abuse coverage but not ordinary materialization behavior. > > Adds large new Vitest blocks for **basic writes** (Skill vs store refs, `"*"`, reconcile actions), **manifest** byte-level contract (sorted keys, no trailing newline, `\uXXXX` escaping, unknown field preservation), **reconcile/prune** semantics, **root and argument validation**, **atomic rename/unlink** (including `interceptUnlink` and `assertAtomicRenameOf`), **resilience** (`onUnavailable`, timeouts, no prune on store outage, no retries), **verify-then-write**, **orphaned temp sweep** (narrow pattern, corrupt manifest suppresses sweep), and the **write-side telemetry** allowlist (Materialized, Revoked, Integrity; no paths or bodies). > > Shared harness additions: `RecordingEmitter`, `_setStore` / `_setEmitterForTesting`, telemetry signal constants, and a **test hygiene** check that `fsOps` mocks are restored between tests. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1c2c602. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
XieX
force-pushed
the
xie/skills-07-docs
branch
from
September 18, 2026 19:19
50721ca to
bfb40f6
Compare
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Last of seven. Documents the feature now that all of it exists. The README section and the contributor notes both span every layer in the stack, so they land once here rather than as six partial edits to the same two files.
README.mdThe public API — the accessors,
writeSkills, the store seam, the exported constants, and the types — plus three things stated plainly rather than left for a reader to discover:Content delivery is not wired up in this release. Everything runs against
SkillStore, the shipped default is absent, and the accessors throw an actionable error until one is configured.Skill content is opaque to the SDK. A verified
Skillcarriescontentas aUint8Array— the exact bytes that were hashed — and the SDK never parses, decodes, or interprets it. There is deliberately no frontmatter accessor and no YAML dependency; the docs show the one-lineTextDecoderstep for consumers who want a string, and leave frontmatter parsing to them.The security posture, including its limitation. Node exposes no
renameat/unlinkat, so the final rename cannot be performed relative to a pinned directory descriptor. An attacker who already has write permission on the managed root can still win a race to redirect a write or a delete outside it. Keep the managed root writable only by the process running the SDK.The one narrow exception to the reconcile's conservatism, stated where the conservatism itself is described: a colliding file whose bytes already are the resolved content is adopted rather than refused, so a reconcile killed between the content writes and the final manifest write heals itself instead of wedging those skills forever. Nothing is overwritten, because nothing needs to be — the only file ever adopted is already byte-identical to what LaunchDarkly resolved. Differing bytes, and bytes that cannot be read at all, are still refused and left untouched.
The two key constraints the filesystem layer imposes that the data model does not — the 255-byte path-component bound and the 22 Windows reserved device names (
con,prn,aux,nul,com1–com9,lpt1–lpt9) — including that a key likeauxstays valid everywhere else, so an AI Config referencing it still parses and its other skills still materialize. Plus the residual the SDK cannot check for a caller: the bound is per component, not on the total path, so a deep root and a long key can still exceed Windows' 260-characterMAX_PATH. One sentence, deliberately not a check — the root is the customer's.agents.mdWhat a future change has to preserve:
types←skills-core←skills, andtypes+safe-fs+skills-core←skills-fs— and why closing a cycle there would let the accessor and filesystem layers disagree about whether a store is configured.Uint8Arrayof the verified verbatim bytes, opaque to the SDK, with the single wire-string encode living inside integrity verification and nowhere else.Four new pitfalls, each a mistake that would otherwise pass review: interpreting skill content anywhere in the SDK (a parser, a convenience accessor, an encoding assumption), reading
writeSkills'stimeoutas milliseconds, relaxing a path or manifest check inskills-fs.ts, and bypassingfsOpsfor a destructive call — which makes the operation invisible to the assertions that prove no operation was attempted, so they then pass vacuously.The path-check pitfall now carries the three specifics a later contributor is most likely to widen without reading the reasoning first:
keyRejectionReasonrather than in the key grammar: a grammar-level rejection would invalidate the whole AI Config for a customer who never touches Windows, and a droppedskillRefsentry lets prune delete the on-disk copy — turning "fails to write on Windows" into "gets deleted on Linux".skipped_currentis reused deliberately, since a newReconcileActionKindmember would break consumers with an exhaustiveswitch.readRegularFileis load-bearing,O_NONBLOCKmost of all, because opening a FIFO with no writer blocks forever — and that the sweep derives its filename pattern fromtempNamePatternrather than copying it.Also here
One pre-existing test renamed to describe its behaviour directly instead of citing a specification section.
getSkillResult(security review LA-2)README.mdgains the accessor in the Agent Skills table,SkillOutcome/SkillOutcomeReasonin Shared Types, and a Fail closed on tampering subsection under the observability material — a worked example exiting onintegrity_failurewhile treatingabsentas ordinary, a table of the five tokens, and an explicit statement thatgetSkillis unchanged and that the two accessors differ only in what they report.agents.mdrecords the five-token vocabulary, why it is coarser thanIntegrityReasonCode, and the fullResolution→ reason mapping including the twoskills-fs.tsrows — so a later contributor adding a sixth internal outcome has to decide which public token it maps to rather than defaulting toabsent. Plus the rule that the version goes intogetObjectwhile the equality check stays a defense, and thatgetSkill's contract is frozen.Verification
Workspace 1172 passing (+2 skipped).
typecheck,biome, andsherifclean.🤖 Generated with Claude Code
Note
Overview
Documentation-only PR that publishes the Agent Skills stack for
@launchdarkly/ai-server: a large Agent Skills section inREADME.mdand matching contributor guidance inagents.md, after the feature landed in earlier stacked PRs.The README now documents the public surface (
skillRefs,getSkill/getSkillResult,writeSkills,SkillStore/InMemorySkillStore), states that remote content delivery is not wired yet (store must be configured), and spells out security/reconcile behavior (hash verification, manifest reconcile,ld.skills.integrity_failureSIEM logging, fail-closed tampering handling). It also updatesinitClient(client, options?)(optionalskillStoreon BYOC) and adds Agent Skills rows to the shared types table.agents.mdadds the skills module layout, one-way dependency rules, a Dependencies table,skillStorelifecycle notes, new pitfalls (opaqueUint8Arraycontent,writeSkillstimeout in seconds, non-relaxable fs checks,fsOps), and extended invariants (telemetry allowlist, vocabularies, frozengetSkillcontract).A single test in
client.test.tsis renamed to drop a spec section reference; no runtime behavior changes in this diff.Reviewed by Cursor Bugbot for commit bfb40f6. Bugbot is set up for automated code reviews on this repo. Configure here.