Skip to content

Port Stele promotion to the current ledger API; install promoted skills; clean-clone tests (#1, #2, #8) - #10

Closed
Deep-Sixed wants to merge 3 commits into
masterfrom
claude/friendly-curie-7inb1h
Closed

Deep-Sixed wants to merge 3 commits into
masterfrom
claude/friendly-curie-7inb1h

Conversation

@Deep-Sixed

@Deep-Sixed Deep-Sixed commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Fixes #1, fixes #2, fixes #8. Also pins mcp below 2 so the server starts on a fresh install.

#1: EvecorStelePort against the current Stele ledger API

The port called create_pending(..., duplicate_policy=...), store.commit() and DuplicateArtifactError. Stele's ledger redesign (Deep-Sixed/Stele#12) removed all three, so agentsync.mcp.server failed at import.

  • The port now opens LedgerStore(db, BlobStore(archive_root)) and records the candidate with create_pending(parser=ParserIdentity("agentsync-kanon", <version>), parser_config={validation_level, dir_name, file_name}), then seal(). There is no input snapshot, because Kanon parsed no input document.
  • A SEALED record is reported as PromotionState.COMMITTED, so the MCP tool output (state: "committed") is unchanged.
  • Retries. Stele allows one record per run_id, and the obligation's run_id is that key. A second attempt with the same run_id reuses the existing record:
    • sealed with the same content → committed (idempotent);
    • still pending with the same content → sealed now;
    • anything else → failed, and the existing artifact is not overwritten.
  • The old "identical content returns another run's record" path is gone. Every run now gets its own record, found with get_by_run_id(obl.run_id).
  • If stele is missing or incompatible, the server now raises the real ImportError with its cause, instead of a generic message.
  • [stele] is pinned to git+https://github.com/Deep-Sixed/Stele@10bbf3f, replacing the unpinned file:///mnt/... path. The README says how to use a local checkout instead.
  • New storage directory storage/stele/archive/ (env var AGENTSYNC_STELE_ARCHIVE_ROOT). It is covered by the bootstrap, backup and restore scripts, .gitignore, and the env example. The ledger's records point into it, so the two are backed up together.

#2: promoted skills land in the approved catalog

  • After Stele seals, PromotionAdapter installs the candidate at approved/<dir_name>/SKILL.md. It writes a temp file and renames it into place, rejects any dir_name that isn't a single directory name, then calls on_installed (the server passes SkillServer.reload).
  • If approved/<dir_name> already holds different content, the promotion is rejected as a validation error before Stele is called. Installing identical content again is allowed.
  • The obligation now records redeemed_skill_id. When enforcing a task, the Enforcer looks up the skill that task already produced by its ID, so re-enforcing a redeemed task returns covered and no second obligation is minted. If that skill has since been removed, the task is obligated again.
  • The server shares one SkillServer between the skill tools and the Enforcer's lookup; before this change they were two indexes that were never reloaded.
  • PromotionAdapter without approved_root behaves as before, so existing callers and tests are unaffected.

#8: the test suite passes on a clean clone

  • Seed skills are committed test fixtures. They live under tests/fixtures/approved/: sailpoint-joiner-provisioning, jamf-ade-enrollment, a broken-skill with no frontmatter, and a directory with no SKILL.md. The skill server, enforcer, local-service and MCP-service tests read from there, never from the git-ignored storage/skills/approved/.
  • Stele is optional for running tests. The Stele port, local-service and MCP-service tests, and the storage test that imports the server, use pytest.importorskip("stele"). Without the [stele] extra they skip; before, they errored during collection.
  • The ContextForge suites already skipped themselves when no gateway is running. That is unchanged.
  • README: the Tests section describes the clean-clone behaviour.

mcp pinned below 2

mcp[cli]>=1.0 resolved to mcp 2.x. That version renamed FastMCP to MCPServer, so the server failed at startup with ModuleNotFoundError: mcp.server.fastmcp. The mcp and dev extras are now mcp[cli]>=1.0,<2, which resolves to 1.30.0 and imports FastMCP. Porting to the 2.x API can be done separately.

Tests

Run on a fresh clone of this branch, in a new venv, under Python 3.12:

Environment Result
core + mcp<2 + pytest, no Stele 83 passed, 14 skipped, 0 failed
the same, plus the pinned Stele ([stele] extra) 102 passed, 10 skipped, 0 failed
master, for comparison (with Stele 10bbf3f and mcp<2) 15 failed, 67 passed, 6 errors
  • With Stele installed, the only skips are the ContextForge suites, which need a live gateway and bearer token.
  • The run leaves the repo's storage/ unchanged.

New or changed tests:

  • tests/test_evecor_stele_port.py: ported to the current API. Adds tests for one record per run, idempotent retry, different content on retry → failed, resuming a pending record, and the recorded producer identity.
  • tests/test_promotion_catalog.py (new, runs without Stele): install plus reload closes the loop, removing the skill brings the obligation back, a conflicting dir_name is rejected before Stele, a failed commit installs nothing, and paths outside approved/ are rejected.
  • tests/test_mcp_service_smoke.py: the full MCP loop now also checks that the promoted skill is listed and that re-enforcing the task returns covered. It copies the fixture catalog into tmp_path, so promotion never writes into the repo.
  • tests/test_operational_hardening.py: covers the archive directory in bootstrap, backup/restore and _ensure_storage_dirs.

Not in this PR

  • If a Stele seal fails, that run's run_id is used up. A later retry of the same obligation gets failed until the obligation is cancelled and a new one minted. Minting a new run_id for each attempt would fix this, but it changes the obligation model.
  • Add record_external_artifact: a supported API for artifacts made outside a sandbox Stele#49 adds a supported record_external_artifact API for this kind of caller. Once it merges, the port can switch to it and move the Stele pin.
  • requires-python = ">=3.14" is unchanged. These results are from 3.12, which is what this environment has.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VnaMtsgmbeB8QWCrqdpSWc

Fixes #1: EvecorStelePort targeted a Stele LedgerStore API removed in the
ledger redesign (commit/DuplicateArtifactError/duplicate_policy), so the
MCP server failed at import. It now records the candidate with
create_pending(parser=agentsync-kanon, ...) and seal(), against a
LedgerStore bound to a BlobStore evidence archive. SEALED is reported as
PromotionState.COMMITTED. A retry for the same run_id reuses its record
(sealed + same content -> committed; pending -> re-sealed; otherwise
failed) instead of recording a second artifact. The stele extra is now
pinned to a Deep-Sixed/Stele commit, and a missing/incompatible stele
surfaces its real ImportError.

Fixes #2: promotion never reached the approved catalog. After Stele
seals, Kanon installs the candidate at approved/<dir_name>/SKILL.md
(atomic write; a different existing skill is rejected before Stele is
called), reloads the Skill Server, and records redeemed_skill_id on the
obligation. The Enforcer shares the server's SkillServer index and looks
up a task's redeemed skill by id, so re-enforcing the task is covered
instead of minting a new obligation.

Storage scripts, env example and README cover the new
storage/stele/archive directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VnaMtsgmbeB8QWCrqdpSWc
mcp 2.x renamed FastMCP to MCPServer, so with `mcp[cli]>=1.0` a fresh
install resolves to 2.x and agentsync.mcp.server fails at startup
(ModuleNotFoundError: mcp.server.fastmcp). Cap the mcp and dev extras at
<2 until the server is ported to the 2.x API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VnaMtsgmbeB8QWCrqdpSWc
Tests read seed skills from storage/skills/approved/, which .gitignore
excludes, so a fresh clone failed 12 tests; and four modules imported
stele at module load, so without the [stele] extra they errored during
collection.

- Add committed seed skills under tests/fixtures/approved/
  (sailpoint-joiner-provisioning, jamf-ade-enrollment, a frontmatter-less
  broken-skill, and a directory without SKILL.md) and point the skill
  server, enforcer, local-service and MCP-service tests at them.
- Skip the Stele port, local-service and MCP-service tests, and the
  server-import storage test, with importorskip("stele") when the
  [stele] extra is not installed.
- README: describe the clean-clone behaviour.

Clean clone, no stele: 83 passed, 14 skipped. With the pinned stele:
102 passed, 10 skipped (ContextForge suites only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VnaMtsgmbeB8QWCrqdpSWc
@Deep-Sixed Deep-Sixed changed the title Port Stele promotion to the current ledger API; install promoted skills (#1, #2) Port Stele promotion to the current ledger API; install promoted skills; clean-clone tests (#1, #2, #8) Sep 26, 2026
@Deep-Sixed
Deep-Sixed force-pushed the claude/friendly-curie-7inb1h branch from 7e4903c to 6563e58 Compare September 26, 2026 07:43
@Deep-Sixed Deep-Sixed closed this Sep 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants