106 lines
7.3 KiB
Python
106 lines
7.3 KiB
Python
from pathlib import Path
|
|
import re
|
|
import unittest
|
|
|
|
|
|
COMMENT = re.compile(r"<!--.*?-->", re.DOTALL)
|
|
|
|
|
|
def task_section(skill: str) -> str:
|
|
operative = COMMENT.sub("", skill)
|
|
return operative.split("## Task and Dependency Breakdown", 1)[1].split("## Version Allocation", 1)[0]
|
|
|
|
|
|
def readiness_section(skill: str) -> str:
|
|
operative = COMMENT.sub("", skill)
|
|
return operative.split("## Delivery Readiness State", 1)[1].split("## Kanban Handoff", 1)[0]
|
|
|
|
|
|
def policy_failures(section: str, approval_rule: str) -> list[str]:
|
|
section = COMMENT.sub("", section)
|
|
patterns = {
|
|
"ownership": r"(?m)^Architecture owns complete Task decomposition for every Feature allocated to the release\.$",
|
|
"lifecycle": r"(?m)^1\. Architecture creates missing Tasks, reviews existing Tasks, and retains, revises, supersedes, or removes them according to the current Feature outcomes and Architecture boundaries\. Architecture may freely revise or remove only draft Tasks with no implementation approval, Kanban admission, execution, or completion evidence;$",
|
|
"cardinality": r"(?m)^2\. define between two and six independently executable Tasks per Feature; permit one Task only when a recorded Architecture rationale proves it is the smallest atomic, independently verifiable boundary;$",
|
|
"mapping": r"(?m)^3\. map the Feature's Tasks collectively, exactly, and reciprocally to every acceptance-outcome ID and every applicable active `FR-\*` and `NFR-\*` record;$",
|
|
"dependencies": r"(?m)^4\. encode acyclic within-Feature sequencing and require every entry Task to depend on all terminal Tasks of every prerequisite Feature;$",
|
|
"contract": r"(?m)^5\. give each Task one specific outcome, repository/component owner, canonical/display identity, task-specific implementation artifacts, concrete inputs and outputs, failure boundaries, exclusions, verification steps, and future acceptance-evidence requirements;$",
|
|
"separate-gates": r"(?m)^6\. keep draft Tasks separate from implementation approval and Kanban Focus admission\.$",
|
|
"planning-readiness": r"(?m)^Architecture must not report planning readiness while any Feature allocated to the release lacks this complete Task breakdown\. A Feature-shaped placeholder Task, a generated empty Task group, or requirement coverage without executable Tasks does not satisfy the gate\.$",
|
|
"protected-history": r"(?m)^Approved, Kanban-admitted, in-progress, or completed Tasks must not be deleted or silently rewritten\. Retain an obsolete Task with a terminal Superseded or Cancelled status, its prior evidence and history, and explicit replacement links\.$",
|
|
"approval-invalidation": approval_rule,
|
|
}
|
|
return [name for name, pattern in patterns.items() if re.search(pattern, section) is None]
|
|
|
|
|
|
def readiness_failures(section: str) -> list[str]:
|
|
patterns = {
|
|
"sequence": r"`IN_DESIGN → READY_FOR_DELIVERY → IN_PROGRESS`",
|
|
"implementation-approval": r"exact item-specific `Approved for Implementation`",
|
|
"complete-inputs": r"complete task inputs",
|
|
"entry-dependencies": r"all entry dependencies are satisfied",
|
|
"ui-ux-evidence": r"approved UI/UX implementation-handoff evidence",
|
|
"ui-ux-not-applicable": r"explicitly records UI/UX as not applicable",
|
|
"not-execution": r"does not mean Focus admission, assignment, claim, implementation start, `delivery_started`, or `IN_PROGRESS`",
|
|
"kanban-admission": r"handoff to Kanban for a separate exact-task Focus-admission decision",
|
|
"scope-schema": r"must not silently mutate or outrun Scope-owned canonical lifecycle schema",
|
|
"scope-fallback": r"return the handoff to Scope for canonicalization and keep the Task in its prior state",
|
|
}
|
|
return [name for name, pattern in patterns.items() if re.search(pattern, section) is None]
|
|
|
|
|
|
class TaskBreakdownPolicyTest(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.skill = Path(__file__).parents[1].joinpath("SKILL.md").read_text()
|
|
cls.task_section = task_section(cls.skill)
|
|
cls.approval_rule = (
|
|
r"(?m)^A material change to Task scope, outcome, dependency, repository, or component invalidates the Task's existing implementation approval, returns the Task to its pre-approval state, and always requires renewed human `Approved for Implementation` before implementation can resume\. After renewed approval, execution still requires a separate exact-task Kanban Focus admission\.$"
|
|
)
|
|
|
|
def test_operational_policy_is_complete_and_structured(self):
|
|
self.assertIn("version: 1.8.0", self.skill)
|
|
self.assertEqual(policy_failures(self.task_section, self.approval_rule), [])
|
|
|
|
def test_comments_and_weakened_rules_cannot_satisfy_policy(self):
|
|
self.assertTrue(policy_failures(f"<!--{self.task_section}-->", self.approval_rule))
|
|
mutations = {
|
|
"draft-protection": ("with no implementation approval, Kanban admission, execution, or completion evidence", "even with implementation approval, Kanban admission, execution, or completion evidence"),
|
|
"planning-readiness": ("must not report planning readiness", "may report planning readiness"),
|
|
"placeholder-rejection": ("A Feature-shaped placeholder Task, a generated empty Task group, or requirement coverage without executable Tasks does not satisfy the gate.", ""),
|
|
"cardinality": ("between two and six", "at least two"),
|
|
"mapping": ("exactly, and reciprocally", "loosely"),
|
|
"dependencies": ("all terminal Tasks", "some Tasks"),
|
|
"approval": ("invalidates the Task's existing implementation approval", "invalidates prior readiness evidence"),
|
|
"renewal": ("always requires renewed human", "may require renewed human"),
|
|
}
|
|
for name, (old, new) in mutations.items():
|
|
with self.subTest(name=name):
|
|
self.assertTrue(policy_failures(self.task_section.replace(old, new), self.approval_rule))
|
|
|
|
def test_ready_for_delivery_is_a_fail_closed_pre_execution_state(self):
|
|
section = readiness_section(self.skill)
|
|
self.assertEqual(readiness_failures(section), [])
|
|
|
|
def test_ready_for_delivery_gate_cannot_be_weakened(self):
|
|
section = readiness_section(self.skill)
|
|
mutations = [
|
|
("exact item-specific `Approved for Implementation`", "implementation is expected"),
|
|
("complete task inputs", "partial task inputs"),
|
|
("all entry dependencies are satisfied", "entry dependencies are expected to be satisfied"),
|
|
("approved UI/UX implementation-handoff evidence", "a UI/UX discussion"),
|
|
("explicitly records UI/UX as not applicable with a rationale", "assumes UI/UX is not applicable"),
|
|
("separate exact-task Focus-admission decision", "automatic Focus admission"),
|
|
("does not mean Focus admission", "means Focus admission"),
|
|
("return the handoff to Scope for canonicalization and keep the Task in its prior state", "continue with a local readiness state"),
|
|
]
|
|
for old, new in mutations:
|
|
with self.subTest(old=old):
|
|
changed = section.replace(old, new)
|
|
self.assertNotEqual(changed, section)
|
|
self.assertTrue(readiness_failures(changed))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|