hns-lsel-applier — LSEL APPLY engine
Namespace:
hns-lsel-*is user-owned dogfood (CLAUDE.local.md §24). This skill is NOT mirrored intointernal/template/templates/— it lives only in this repo. Graduation tomoai-lsel-*+ 16-language distribution is a separate SPEC (out of scope per spec.md §G).
Token discipline. This body deliberately avoids the literal identifier of the frozen Go applier's write-flag and the literal name of the orchestrator-only user-question channel. Both are referenced by location and role (e.g. "the write-flag at
internal/harness/applier.go:22", "the orchestrator's synchronous user gate") so the REQ-LSEL-003 invariant-grep and the E4 subagent-boundary grep over LSEL surfaces read zero literal matches. The invariants themselves are stated in plain English.
What this skill does
The LSEL loop's PROPOSE→APPLY seam was dead in production: Applier.Apply never ran
(manifest.jsonl absent), CuratorDispatch had 0 production callers, and the frozen
Go applier's write-flag at internal/harness/applier.go:22 (kept false) is the
apply dead-switch (REQ-LSEL-003 — M3 keeps it false by BYPASS, never unfreeze). The
M3 closure routes APPLY through a parallel user-owned applier that writes only to
the six evolvable surfaces (spec.md §B.3), while the frozen Go applier stays
byte-for-byte frozen. Design SSOT:
.moai/reports/moai-local-self-evolution-design-20260804.html §4 ("동결된 Go applier를
우회하는 병렬 사용자 소유 applier를 세운다" — build a parallel user-owned applier that
bypasses the frozen Go one) + §7 (the two-round critique moved the allowlist OUT of
the evolvable skill files into a frozen meta file, and added execution-meta files to
the forced-gate set).
The APPLY engine is the playback hook .moai/hooks/lsel-apply.sh plus this skill's
model-mediated judgment (target validation nuance, approval-marker provenance, blast-
radius reasoning the mechanical hook cannot see). The hook is the load-bearing safety
floor; this skill is the consumer-facing surface that decides WHICH approved decision
to feed it next and how to interpret refusal.
The APPLY pipeline
lsel-apply.sh <decision.json> performs five steps. Steps 1-2 are the safety floor;
steps 3-5 are the playback.
- Frozen-allowlist hard-reject (REQ-LSEL-001 / AC-LSEL-001). The target path in
decision.jsonis matched against thefrozen_patternsregex list in.claude/lsel/frozen-allowlist.json. A match → REFUSE: a row is appended to.moai/logs/lsel-reject.lognaming the rejected path and categoryfrozen-path, NO file is written, and the hook exits 2. - Execution-meta forced-gate (REQ-LSEL-002 / AC-LSEL-005 D3 self-amending-
handcuffs). If the target matches one of the four
execution_metacategories — (i) the frozen allowlist meta file itself, (ii) an applier/curator skill body (hns-lsel-applier/,hns-lsel-curator/), (iii) the apply hook (.moai/hooks/lsel-*.sh), (iv) thesettings.local.jsonhook-registration subblock — the hook checksdecision.jsonfor a synchronous-approval marker (asynchronous_approvalobject withdecision: "approved", produced by the orchestrator's synchronous user gate). No marker → REFUSE: reject-log row with categoryexecution-meta, exit 3, no write. With marker → proceeds (the refusal is keyed on the absent marker, NOT on the category match alone). The refusal semantics mirror the M2csa_refusal_test.shfixture exactly. - Apply the diff.patch via
git apply(playback of an already-approved decision). Only the paths declared in the patch are staged — nevergit add -A(working-tree hygiene). - Append the apply-ledger row to
.moai/state/lsel/apply-ledger.jsonl:{proposal_id, target_surface, ts, result:"applied", commit_sha, category}. This is the manifest the frozen Go applier never produced, finally real in user-owned space. - Commit the staged change as ONE
feat(lsel-<proposal-id>): ...Conventional Commit on the current (feature) branch (REQ-LSEL-004). The ledger row'scommit_shais backfilled from the commit's short SHA.
A no-arg invocation is a clean no-op (exit 0) so an empty approved-queue does not derail a loop pass.
The model-mediated layer (you, when invoked)
When this skill is invoked to drive an APPLY pass, your job on top of the mechanical hook is:
- Read the proposal's
proposal.md+self-critique.mdat.moai/state/lsel/proposals/<id>/. A proposal withstatus: blocked(any UNRESOLVED self-critique objection) MUST NOT be fed to the hook — return a blocker report instead. - Confirm the approval marker's provenance. The
synchronous_approvalobject must carry a real orchestrator-produced approval artifact (the synchronous user-question channel the orchestrator owns per askuser-protocol.md). A marker the loop fabricated for itself is the self-amending-handcuffs failure mode (REQ-LSEL-002); the hook's mechanical check is the floor, your provenance judgment is the ceiling. This skill is a subagent mechanism and NEVER invokes the orchestrator-only user-question channel; return a blocker report and let the orchestrator run the gate. - Re-verify the frozen-allowlist invariant by grepping the write-flag at
internal/harness/applier.go:22— it MUST stayfalse. If M3's bypass ever drifts toward unfreezing the Go applier, return a blocker (AP-LSEL-002).
What this skill does NOT do
- No unfreezing of the Go applier — the write-flag at
internal/harness/applier.go:22staysfalse(REQ-LSEL-003). The bypass is parallel and user-owned; the frozen applier is reference-only. - No edits to frozen doctrine —
internal/template/templates/**,.claude/rules/moai/**,AGENTS.md, retained agents,moai-*skills, the frozen Go applier /curator_dispatch.go, and.moai/config/sections/**are byte-for-byte untouched. The allowlist hard-rejects them (step 1). - No new-apply / self-approve / allowlist-amend primitive in
lsel-apply.sh(REQ-LSEL-008). The hook only consumes already-approved decisions; it does not author proposals or bless them. The M4verify.shruns only AFTER an apply has committed — it does not create a new apply; it never self-approves. - No user-question invocation — subagent boundary (askuser-protocol.md). Return a blocker report; the orchestrator runs the synchronous gate.
VERIFY stage (M4 — REQ-LSEL-013 / AC-LSEL-015)
After an apply commits a proposal, VERIFY proves the apply was safe. VERIFY has two layers — both MANDATORY:
(a) Mechanical layer — verify.sh (bash-testable)
verify.sh --proposal-dir <p> --repo-root <r> [--timeout SECS] [--feedback-file <f>]
runs the proposal's frontmatter verify_command with a timeout-retry-once
policy and auto-reverts on a second failure:
- 2-attempt policy. Attempt 1 runs. On SUCCESS →
verified:true, stop. On TIMEOUT-class failure → attempt 2 (flaky tolerance — report §10 P3: "retry once on timeout so a correct proposal isn't flipped by noise"). On NON-TIMEOUT-FAIL → attempt 2 (allows the "second non-timeout failure" of AC-LSEL-015 clause 3 to materialize before revert fires). - Attempt 2: SUCCESS →
verified:true. Any failure (timeout OR non-timeout) →verified:false+ autogit revert lsel-<proposal-id>+ the proposal'sfeedback_*.mdis markedverified: false. - The outcome is appended to the apply ledger as a
{"stage":"verify","verified":true|false,...}row — the load-bearing signal.
(b) MANDATORY /moai gate superset (model-mediated)
/moai gate (lint+format+type+test) is MANDATORY after every apply — it is
NOT optional. A bash hook cannot invoke a Claude Code slash command directly,
so the gate runs MODEL-SIDE: when this skill drives an APPLY pass, the
orchestrator/model runs /moai gate after the mechanical apply + verify. The
proposer-authored verify_command alone is circular (report §11 mustFix
B#6 / AP-LSEL-004): a proposer can author a verify_command that its own change
satisfies. /moai gate is the independent check — lint, format, type-check,
and the test suite exercise surfaces the proposal never touched. Treat a
mechanical verify_command PASS without a green /moai gate as an unverified
apply.
Model-mediated layer (you, when invoked)
- After
lsel-apply.shcommits andverify.shreports, run/moai gate(the MANDATORY superset). If the gate fails, the apply is unverified even ifverify.shreportedverified:true— treat it as a VERIFY failure and revert. - A revert (whether auto-fired by
verify.shor gate-driven) MUST be surfaced to the orchestrator as a blocker report — the orchestrator, not this skill, confirms a revert with the user. Never run/moai gate's user-facing surface from inside this subagent mechanism; return a blocker report.
Verification (run before declaring an APPLY+VERIFY pass complete)
# M4 VERIFY characterization test (AC-LSEL-015) — hermetic temp repo:
.claude/skills/hns-lsel-applier/verify_test.sh
# the verify ledger row carries the outcome:
grep '"stage":"verify"' .moai/state/lsel/apply-ledger.jsonl
Verification (run before declaring an APPLY pass complete)
# 1. APPLY characterization test (AC-001/002/005/008/013) — hermetic temp repo:
.claude/skills/hns-lsel-applier/apply_test.sh
# 2. Rollback-rehearsal SHIP GATE (AC-014):
.claude/skills/hns-lsel-applier/rollback_rehearsal_test.sh
# 3. REQ-LSEL-003 frozen-flag re-verify (post-apply). The frozen Go applier's
# write-flag at internal/harness/applier.go:22 MUST read `false`, and no LSEL
# surface (skills/hooks/state/allowlist) references that identifier as mutable.
# (The literal identifier is intentionally not written here; find it at line 22
# of the frozen file and grep LSEL surfaces for it — expect zero hits.)
sed -n '22p' internal/harness/applier.go # MUST show the `= false` write-flag line
# 4. The apply ledger carries the new row:
tail -1 .moai/state/lsel/apply-ledger.jsonl
Cross-references
- SPEC:
.moai/specs/SPEC-LSEL-LOCAL-EVOLUTION-001/{spec,plan,acceptance,progress}.md - Design report (SSOT):
.moai/reports/moai-local-self-evolution-design-20260804.html§4 (user-owned parallel applier), §7 (allowlist relocation + execution-meta forced gate), §10 P3, §11 mustFix A#1/A#5/A#8. - Frozen applier (reference only):
internal/harness/applier.go:22(the frozen write-flag),internal/harness/curator_dispatch.go. - Curator (PROPOSE stage, M2):
.claude/skills/hns-lsel-curator/SKILL.md(CSA forced-gate doctrine + the Tier-4 DEAD finding). - CSA refusal fixture:
.claude/skills/hns-lsel-curator/csa_refusal_test.sh(the refusal rule this hook enforces mechanically). - Namespace guard:
internal/template/split_namespace_test.go,internal/template/internal_content_leak_test.go(must NOT flag ahns-lsel-applierleak — this skill is dogfood, never templated).