plm-skill-kernel 1.3.0__tar.gz → 1.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/PKG-INFO +1 -1
  2. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/__init__.py +1 -1
  3. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/__init__.py +5 -0
  4. plm_skill_kernel-1.4.0/plm_skill_kernel/executor/_catalog_fixture.py +102 -0
  5. plm_skill_kernel-1.4.0/plm_skill_kernel/executor/_demo_ids.py +18 -0
  6. plm_skill_kernel-1.4.0/plm_skill_kernel/executor/_demo_manifest.py +146 -0
  7. plm_skill_kernel-1.4.0/plm_skill_kernel/executor/_demo_skills.py +195 -0
  8. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/plan.py +8 -0
  9. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/PKG-INFO +1 -1
  10. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/SOURCES.txt +4 -0
  11. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/pyproject.toml +1 -1
  12. plm_skill_kernel-1.4.0/tests/test_demo_skill_package.py +261 -0
  13. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_executor.py +1 -1
  14. plm_skill_kernel-1.3.0/plm_skill_kernel/executor/_catalog_fixture.py +0 -80
  15. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/README.md +0 -0
  16. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/__main__.py +0 -0
  17. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/__init__.py +0 -0
  18. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_cli.py +0 -0
  19. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_output_safety.py +0 -0
  20. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_serialize.py +0 -0
  21. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_terminal.py +0 -0
  22. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_verdict.py +0 -0
  23. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_version.py +0 -0
  24. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/exit_codes.py +0 -0
  25. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/models.py +0 -0
  26. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/protocol.py +0 -0
  27. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/dispatcher.py +0 -0
  28. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/binding.py +0 -0
  29. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/executor.py +0 -0
  30. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/knowledge.py +0 -0
  31. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/request.py +0 -0
  32. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/selector.py +0 -0
  33. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/mocks/__init__.py +0 -0
  34. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/mocks/connectors.py +0 -0
  35. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/mocks/engine_core_stub.py +0 -0
  36. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/mocks/foundation_seed.py +0 -0
  37. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/mocks/knowledge_services_port.py +0 -0
  38. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/registry.py +0 -0
  39. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/sdk/__init__.py +0 -0
  40. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/sdk/_registry.py +0 -0
  41. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/sdk/context.py +0 -0
  42. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/sdk/decorator.py +0 -0
  43. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/sdk/types.py +0 -0
  44. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/server.py +0 -0
  45. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/__init__.py +0 -0
  46. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/agents/__init__.py +0 -0
  47. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/agents/suggest.py +0 -0
  48. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/bpmn/__init__.py +0 -0
  49. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/bpmn/generate.py +0 -0
  50. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/cleansing/__init__.py +0 -0
  51. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/cleansing/dedupe.py +0 -0
  52. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/cleansing/normalise.py +0 -0
  53. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/documents/__init__.py +0 -0
  54. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/documents/parse.py +0 -0
  55. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/__init__.py +0 -0
  56. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/_v1_corpus.py +0 -0
  57. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/get_record.py +0 -0
  58. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/resolve.py +0 -0
  59. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/retrieve.py +0 -0
  60. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/search.py +0 -0
  61. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/validate.py +0 -0
  62. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/__init__.py +0 -0
  63. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/knowledge_pack_loader.py +0 -0
  64. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/knowledge_search_engine.py +0 -0
  65. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/plm_skill_registry.py +0 -0
  66. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/plm_tool_definitions.py +0 -0
  67. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/skill_orchestrator.py +0 -0
  68. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/dependency_links.txt +0 -0
  69. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/entry_points.txt +0 -0
  70. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/requires.txt +0 -0
  71. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/top_level.txt +0 -0
  72. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/setup.cfg +0 -0
  73. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_agents_suggest_asgi.py +0 -0
  74. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_agents_suggest_unit.py +0 -0
  75. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_connector_read_mock.py +0 -0
  76. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_dispatcher.py +0 -0
  77. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_documents_parse_asgi.py +0 -0
  78. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_documents_parse_unit.py +0 -0
  79. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_engine_core_stub_mock.py +0 -0
  80. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_foundation_mocks.py +0 -0
  81. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_knowledge_services_port_mock.py +0 -0
  82. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_sdk_decorator.py +0 -0
  83. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_server.py +0 -0
  84. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_skills_cleansing.py +0 -0
  85. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_skills_knowledge.py +0 -0
  86. {plm_skill_kernel-1.3.0 → plm_skill_kernel-1.4.0}/tests/test_skills_knowledge_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plm-skill-kernel
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: TracePulse PLM Skill Kernel — Wave 2 Conv A. Decorator-based Skill SDK + V1 in-process dispatcher + Kernel HTTP server stub matching CR.10 §7.bis (POST /v1/skills/{id}/invoke). Ships 3 starter skills (cleansing.normalise, cleansing.dedupe, bpmn.generate). Sibling of plm-engine-core; the two communicate over the V1.1 HTTP loopback (SKILL_KERNEL_LOOPBACK=on) — no Python-level coupling at the dispatch boundary.
5
5
  Author: TracePulse
6
6
  License: Proprietary
@@ -14,7 +14,7 @@ sanctioned interaction is the V1.1 HTTP loopback (env-flagged on
14
14
  ``SKILL_KERNEL_LOOPBACK``); any Python-level reach-in is forbidden by
15
15
  the import-linter contract in ``pyproject.toml``.
16
16
  """
17
- __version__ = "1.3.0"
17
+ __version__ = "1.4.0"
18
18
 
19
19
  # The decorator + sdk surface is the public authoring API. Re-exported
20
20
  # here so callers write ``from plm_skill_kernel import skill`` rather
@@ -23,6 +23,11 @@ path; the executor is driven by the demo entrypoint + tests.
23
23
  """
24
24
  from __future__ import annotations
25
25
 
26
+ # NOTE: ``load_demo_skills`` is intentionally NOT re-exported here. Importing it
27
+ # would fire the demo tasks' ``@skill`` decorators as a side effect of importing
28
+ # the executor package, leaking ``demo.foundation.*`` into the SDK registry /
29
+ # federation introspection. Demo registration is a deliberate opt-in — import
30
+ # ``plm_skill_kernel.executor._demo_skills.load_demo_skills`` and call it.
26
31
  from .binding import BindingNotFound, resolve_binding
27
32
  from .executor import SkillExecutor, TaskFailure
28
33
  from .knowledge import EchoKnowledgeProvider, KnowledgeProvider, NullKnowledgeProvider
@@ -0,0 +1,102 @@
1
+ """Demo catalog fixture (FTR-2232 / E1; the certified package is FTR-2235 / E4).
2
+
3
+ # MOCK(MVP-DEMO): this is a demo-scoped skill *catalog*, not the governed
4
+ # skill registry or the ``manifest-profile/1.0`` authoring surface. It maps a
5
+ # composite skill id to its ordered :class:`SkillPlan`. E1 shipped a
6
+ # structurally-valid stub here; **E4** replaces it with the concrete, certified
7
+ # demo skill package: an ordered >= 2-task composite that carries all manifest
8
+ # attributes and runs end-to-end over the E2 pump foundation seam (decision C1).
9
+
10
+ The composite ``demo.knowledge_lookup`` decomposes into two ordered tasks:
11
+
12
+ 1. ``demo.foundation.retrieve`` — the executor injects a broad set of
13
+ foundation snippets (via the task's static ``knowledge_query`` seed); the
14
+ task narrows them to the runtime query.
15
+ 2. ``demo.foundation.synthesize`` — turns the retrieved snippets into a
16
+ grounded, cited answer.
17
+
18
+ GOVERNANCE FIREWALL (FTR-2235 AC): ``certified`` / ``controlled`` and the
19
+ attached :data:`~plm_skill_kernel.executor._demo_manifest.DEMO_MANIFEST` are
20
+ **demo-fixture fakes** — advisory, unenforced, and confined to this layer.
21
+ They must never reach the FTR-2034 register or the published profile.
22
+ """
23
+ from __future__ import annotations
24
+
25
+ from typing import Dict, Tuple
26
+
27
+ from ._demo_ids import DEMO_TASK_VERSION, RETRIEVE_SKILL_ID, SYNTHESIZE_SKILL_ID
28
+ from ._demo_manifest import DEMO_MANIFEST
29
+ from .plan import SkillPlan, TaskStep
30
+
31
+ DEMO_SKILL_ID = "demo.knowledge_lookup"
32
+ DEMO_SKILL_VERSION = "0.1.0"
33
+
34
+ # MOCK(MVP-DEMO): static seed the executor hands the knowledge provider for
35
+ # task 1 — a broad "load the foundation context" query. It is intentionally
36
+ # NOT the runtime query: the executor loads a broad set, then the certified
37
+ # skill's task 1 narrows it to what the caller actually asked. Broad on
38
+ # purpose so re-ranking in the task is meaningful.
39
+ _FOUNDATION_SEED_QUERY = (
40
+ "hydraulic pump foundation reference material standard torque pressure shaft"
41
+ )
42
+
43
+ _DEMO_PLAN = SkillPlan(
44
+ skill_id=DEMO_SKILL_ID,
45
+ version=DEMO_SKILL_VERSION,
46
+ description=(
47
+ "Certified demo composite: retrieve grounding from the foundation "
48
+ "corpus, then synthesize a cited answer — an ordered two-task skill "
49
+ "the agent delegates to instead of answering from itself."
50
+ ),
51
+ # MOCK(MVP-DEMO): fixture governance flags — advisory, not real
52
+ # certification (real status lives in the FTR-2034 register).
53
+ certified=True,
54
+ controlled=True,
55
+ # MOCK(MVP-DEMO): all manifest-profile/1.0 attributes, faked (AC1).
56
+ manifest=DEMO_MANIFEST,
57
+ tasks=(
58
+ TaskStep(
59
+ task_id="retrieve",
60
+ skill_id=RETRIEVE_SKILL_ID,
61
+ version=DEMO_TASK_VERSION,
62
+ description=(
63
+ "Ordered task 1 — narrow the injected foundation snippets to "
64
+ "the runtime query."
65
+ ),
66
+ input_keys=("query",),
67
+ # Triggers foundation injection under payload['foundation'].
68
+ knowledge_query=_FOUNDATION_SEED_QUERY,
69
+ ),
70
+ TaskStep(
71
+ task_id="synthesize",
72
+ skill_id=SYNTHESIZE_SKILL_ID,
73
+ version=DEMO_TASK_VERSION,
74
+ description=(
75
+ "Ordered task 2 — synthesize a grounded, cited answer from "
76
+ "the retrieved snippets."
77
+ ),
78
+ input_keys=("retrieved", "query"),
79
+ output_key="result",
80
+ ),
81
+ ),
82
+ )
83
+
84
+
85
+ # Catalog is keyed by (skill_id, version); a None-version lookup resolves to
86
+ # the sole registered version for that id (demo convenience).
87
+ DEMO_CATALOG: Dict[Tuple[str, str], SkillPlan] = {
88
+ (DEMO_SKILL_ID, DEMO_SKILL_VERSION): _DEMO_PLAN,
89
+ }
90
+
91
+
92
+ def build_demo_catalog() -> Dict[Tuple[str, str], SkillPlan]:
93
+ """Return a fresh copy of the demo catalog (callers may extend it)."""
94
+ return dict(DEMO_CATALOG)
95
+
96
+
97
+ __all__ = [
98
+ "DEMO_CATALOG",
99
+ "DEMO_SKILL_ID",
100
+ "DEMO_SKILL_VERSION",
101
+ "build_demo_catalog",
102
+ ]
@@ -0,0 +1,18 @@
1
+ """Shared ids for the demo composite's tasks (FTR-2235 / E4).
2
+
3
+ Deliberately **dependency-free**. The catalog fixture names the demo tasks by
4
+ these id strings *without* importing the skill module — because importing
5
+ :mod:`plm_skill_kernel.executor._demo_skills` fires its ``@skill`` decorators
6
+ and registers the demo callables into the SDK registry. Keeping the ids here
7
+ means merely importing the ``executor`` package (or the catalog fixture) never
8
+ has that side effect; registration is a deliberate act via
9
+ :func:`plm_skill_kernel.executor._demo_skills.load_demo_skills`, so faked demo
10
+ skills never boot-load or appear in federation introspection unasked.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ RETRIEVE_SKILL_ID = "demo.foundation.retrieve"
15
+ SYNTHESIZE_SKILL_ID = "demo.foundation.synthesize"
16
+ DEMO_TASK_VERSION = "1.0.0"
17
+
18
+ __all__ = ["DEMO_TASK_VERSION", "RETRIEVE_SKILL_ID", "SYNTHESIZE_SKILL_ID"]
@@ -0,0 +1,146 @@
1
+ """Demo manifest attributes for the certified composite (FTR-2235 / E4).
2
+
3
+ # MOCK(MVP-DEMO): every value in this module is a **demo-fixture fake**. This
4
+ # is the "carries all manifest attributes" surface of the demo skill package
5
+ # (AC1) — it mirrors the *field names* of the governed
6
+ # ``manifest-profile/1.0`` (read-only source:
7
+ # ``plm_skill_packages/manifest_profile/data/v1_profile.yaml``) so the demo
8
+ # fixture is recognisably a full manifest, while every value is a stand-in.
9
+ #
10
+ # GOVERNANCE FIREWALL (FTR-2235 AC): the faked ``certified`` / ``maturity``
11
+ # values below live ONLY in this demo fixture layer. They MUST NOT be written
12
+ # to the FTR-2034 decision register or the published ``manifest-profile/1.0``.
13
+ # The value string ``certified_industrial_capability`` is deliberately
14
+ # confined to this module (a firewall guard test asserts it appears nowhere
15
+ # else under ``plm_skill_kernel``).
16
+
17
+ The model is ``frozen`` + ``extra="forbid"`` to mirror the discipline of the
18
+ governed profile, but it is a demo descriptor — it is never validated against,
19
+ or promoted into, the real profile. It carries no behaviour; the executor
20
+ never reads it (AC4: no conformance gating).
21
+ """
22
+ from __future__ import annotations
23
+
24
+ from typing import Tuple
25
+
26
+ from pydantic import BaseModel, ConfigDict, Field
27
+
28
+ # The faked maturity/lifecycle/autonomy values. Named constants so the intent
29
+ # is legible and the firewall guard has one obvious place to look.
30
+ # MOCK(MVP-DEMO): a real certified maturity requires human sign-off in the
31
+ # FTR-2034 register; this string is a fixture stand-in and nothing else.
32
+ _MOCK_MATURITY_CERTIFIED = "certified_industrial_capability"
33
+ _MOCK_LIFECYCLE_RELEASED = "released"
34
+ _MOCK_AUTONOMY_SUPERVISED = "supervised"
35
+
36
+
37
+ class DemoManifestAttributes(BaseModel):
38
+ """All ``manifest-profile/1.0`` attributes, mirrored as demo metadata.
39
+
40
+ One field per governed profile ``machine_key`` (28 total). Presence — not
41
+ enforcement — is the point: AC1 asks that the demo skill "carries all
42
+ manifest attributes", and AC4 explicitly says the executor does not gate
43
+ on a ``VALID`` conformance verdict, so these values are never validated.
44
+ """
45
+
46
+ model_config = ConfigDict(frozen=True, extra="forbid")
47
+
48
+ # ── identity ──────────────────────────────────────────────────────────
49
+ skill_id: str
50
+ name: str
51
+ mission: str
52
+ semantic_version: str
53
+
54
+ # ── lifecycle / maturity / autonomy (the "certified" story) ───────────
55
+ lifecycle_state: str
56
+ maturity_level: str
57
+ autonomy_ceiling: str
58
+
59
+ # ── scope / ownership / audience ──────────────────────────────────────
60
+ tenant_scope: str
61
+ audience: str
62
+ owner: str
63
+
64
+ # ── governance references (certified-tier fields) ─────────────────────
65
+ minimum_trace_level: str
66
+ hitl_policy_reference: str
67
+ certification_reference: str
68
+ evaluation_reference: str
69
+ pinned_baseline_reference: str
70
+
71
+ # ── contract / schema surface ─────────────────────────────────────────
72
+ contract_hash: str
73
+ input_schema_ref: str
74
+ output_schema_ref: str
75
+
76
+ # ── behavioural contract ──────────────────────────────────────────────
77
+ preconditions: Tuple[str, ...] = Field(default=())
78
+ postconditions: Tuple[str, ...] = Field(default=())
79
+ failure_modes: Tuple[str, ...] = Field(default=())
80
+ evidence_expectations: Tuple[str, ...] = Field(default=())
81
+
82
+ # ── compatibility / dependencies ──────────────────────────────────────
83
+ kernel_api_compatibility: str
84
+ knowledge_api_compatibility: str
85
+ model_configuration: str
86
+ connector_deps: Tuple[str, ...] = Field(default=())
87
+ inference_deps: Tuple[str, ...] = Field(default=())
88
+ knowledge_deps: Tuple[str, ...] = Field(default=())
89
+
90
+
91
+ # MOCK(MVP-DEMO): the concrete faked manifest for the demo composite skill.
92
+ # Certified-tier so the demo can *show* an agent delegating to a certified,
93
+ # controlled skill — every value here is a stand-in, none is ratified.
94
+ DEMO_MANIFEST = DemoManifestAttributes(
95
+ skill_id="demo.knowledge_lookup",
96
+ name="Foundation Knowledge Lookup (DEMO)",
97
+ mission=(
98
+ "Delegate a knowledge request to an ordered, certified composite "
99
+ "skill that retrieves and then synthesizes grounded foundation "
100
+ "reference material — instead of the agent answering from itself."
101
+ ),
102
+ semantic_version="0.1.0",
103
+ lifecycle_state=_MOCK_LIFECYCLE_RELEASED,
104
+ maturity_level=_MOCK_MATURITY_CERTIFIED,
105
+ autonomy_ceiling=_MOCK_AUTONOMY_SUPERVISED,
106
+ tenant_scope="shared",
107
+ audience="internal-demo",
108
+ owner="mvp-demo-owner (MOCK)",
109
+ minimum_trace_level="standard",
110
+ hitl_policy_reference="mock://hitl-policy/none",
111
+ certification_reference="mock://certification/demo-knowledge-lookup@0.1.0",
112
+ evaluation_reference="mock://evaluation/demo-knowledge-lookup@0.1.0",
113
+ pinned_baseline_reference="mock://baseline/foundation-seed@rev-1",
114
+ contract_hash="sha256:0000000000000000000000000000000000000000000000000000000000000000",
115
+ input_schema_ref="mock://schema/demo.knowledge_lookup/input@0.1.0",
116
+ output_schema_ref="mock://schema/demo.knowledge_lookup/output@0.1.0",
117
+ preconditions=(
118
+ "A foundation knowledge provider is wired on the executor "
119
+ "(demo: SeedFoundationKnowledgeProvider behind PLM_MVP_MOCKS).",
120
+ ),
121
+ postconditions=(
122
+ "Returns a grounded answer citing foundation snippet ids, or an "
123
+ "explicitly empty grounding when nothing matched the query.",
124
+ ),
125
+ failure_modes=(
126
+ "No foundation snippet matches the query -> empty grounding, the "
127
+ "composite still succeeds (the demo does not fail closed on no-hit).",
128
+ ),
129
+ evidence_expectations=(
130
+ "Per-task execution trace with per-task knowledge-injection flags.",
131
+ ),
132
+ kernel_api_compatibility="v1",
133
+ knowledge_api_compatibility="v1",
134
+ model_configuration="none (deterministic; no inference in the demo composite)",
135
+ connector_deps=(),
136
+ inference_deps=(),
137
+ knowledge_deps=("foundation-seed (MOCK)",),
138
+ )
139
+
140
+
141
+ def manifest_field_names() -> Tuple[str, ...]:
142
+ """Return the mirrored profile attribute names (for AC1 assertions)."""
143
+ return tuple(DemoManifestAttributes.model_fields.keys())
144
+
145
+
146
+ __all__ = ["DEMO_MANIFEST", "DemoManifestAttributes", "manifest_field_names"]
@@ -0,0 +1,195 @@
1
+ """The certified composite's ordered tasks (FTR-2235 / E4, decision C1).
2
+
3
+ # MOCK(MVP-DEMO): two demo-scoped ``@skill`` tasks that make the composite
4
+ # ``demo.knowledge_lookup`` do real, data-dependent work over the E2 pump
5
+ # foundation seam — one coherent dataset, no ``extra="forbid"`` collision
6
+ # (both tasks read the injected ``payload['foundation']`` freely, unlike the
7
+ # strict ``knowledge.*`` request models).
8
+
9
+ The composite's ordered steps:
10
+
11
+ * **task 1 — ``demo.foundation.retrieve``**: the executor has already loaded a
12
+ broad set of foundation snippets into ``payload['foundation']`` (via the
13
+ task's static ``knowledge_query`` seed). This task *narrows* that set to the
14
+ caller's runtime ``query`` by token overlap and keeps the top matches.
15
+ * **task 2 — ``demo.foundation.synthesize``**: turns the retrieved snippets
16
+ into a short, grounded, pump-themed answer with explicit citations
17
+ (snippet id + title + non-authoritative source label).
18
+
19
+ Both are authored with the ordinary ``@skill`` decorator (Decision #59 shape)
20
+ so they register into the SDK registry and run through the ordinary
21
+ dispatcher — nothing about the executor changes. They are **deliberately kept
22
+ out of** :func:`plm_skill_kernel.registry.load_starter_skills` (the production
23
+ / federation-manifest surface); the demo wires them via
24
+ :func:`load_demo_skills` instead, so faked demo skills never boot-load into a
25
+ real kernel.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import re
30
+ from typing import Any, Dict, List
31
+
32
+ from plm_skill_kernel import SkillContext, skill
33
+ from plm_skill_kernel.sdk._registry import RegisteredSkill, get_registry
34
+
35
+ from ._demo_ids import DEMO_TASK_VERSION, RETRIEVE_SKILL_ID, SYNTHESIZE_SKILL_ID
36
+
37
+ # How many narrowed snippets task 1 forwards to task 2.
38
+ _KEEP_TOP = 3
39
+ _TOKEN_RE = re.compile(r"[a-z0-9]+")
40
+
41
+
42
+ def _tokens(text: str) -> List[str]:
43
+ return _TOKEN_RE.findall((text or "").lower())
44
+
45
+
46
+ def _snippet_blob(snippet: Dict[str, Any]) -> str:
47
+ """Searchable text for a foundation snippet (id/type/title/snippet)."""
48
+ parts = [
49
+ str(snippet.get("id", "")),
50
+ str(snippet.get("type", "")),
51
+ str(snippet.get("title", "")),
52
+ str(snippet.get("snippet", "")),
53
+ ]
54
+ return " ".join(parts).lower()
55
+
56
+
57
+ @skill(
58
+ id=RETRIEVE_SKILL_ID,
59
+ version=DEMO_TASK_VERSION,
60
+ description=(
61
+ "Demo composite task 1: narrow the injected foundation snippets to "
62
+ "the runtime query by token overlap; keep the top matches."
63
+ ),
64
+ min_autonomy="L1",
65
+ )
66
+ async def demo_foundation_retrieve(
67
+ payload: Dict[str, Any], ctx: SkillContext
68
+ ) -> Dict[str, Any]:
69
+ """Rank ``payload['foundation']`` against ``payload['query']``.
70
+
71
+ Reads the foundation snippets the executor injected under the reserved
72
+ ``foundation`` key and re-ranks them for *this* request's query. Returns
73
+ the top matches under ``retrieved`` (shallow-merged into the running
74
+ state for task 2). Never fails closed on a no-hit — an empty ``retrieved``
75
+ is a valid, grounded-in-nothing outcome (AC4: the demo does not gate).
76
+ """
77
+ query = str(payload.get("query", ""))
78
+ foundation = payload.get("foundation") or []
79
+ q = set(_tokens(query))
80
+
81
+ scored: List[tuple] = []
82
+ for snippet in foundation:
83
+ if not isinstance(snippet, dict):
84
+ continue
85
+ blob = _snippet_blob(snippet)
86
+ overlap = sum(1 for tok in q if tok in blob)
87
+ if overlap > 0:
88
+ scored.append((overlap, snippet))
89
+
90
+ # Deterministic order: higher overlap first, then snippet id ascending.
91
+ scored.sort(key=lambda s: (-s[0], str(s[1].get("id", ""))))
92
+ retrieved = [snippet for _, snippet in scored[:_KEEP_TOP]]
93
+
94
+ return {
95
+ "retrieved": retrieved,
96
+ "match_count": len(scored),
97
+ "considered": len(foundation),
98
+ "query_used": query,
99
+ }
100
+
101
+
102
+ @skill(
103
+ id=SYNTHESIZE_SKILL_ID,
104
+ version=DEMO_TASK_VERSION,
105
+ description=(
106
+ "Demo composite task 2: synthesize a short grounded answer from the "
107
+ "retrieved foundation snippets, with explicit citations."
108
+ ),
109
+ min_autonomy="L1",
110
+ )
111
+ async def demo_foundation_synthesize(
112
+ payload: Dict[str, Any], ctx: SkillContext
113
+ ) -> Dict[str, Any]:
114
+ """Compose a grounded, pump-themed answer from ``payload['retrieved']``.
115
+
116
+ The answer text names the top foundation reference(s) and every claim is
117
+ tied to a citation (snippet id + title + the non-authoritative ``source``
118
+ label). If nothing was retrieved, the answer says so plainly and
119
+ ``grounded`` is ``False`` — a legible, honest empty result.
120
+ """
121
+ query = str(payload.get("query", ""))
122
+ retrieved = payload.get("retrieved") or []
123
+
124
+ citations = [
125
+ {
126
+ "id": snippet.get("id"),
127
+ "title": snippet.get("title"),
128
+ "source": snippet.get("source"),
129
+ }
130
+ for snippet in retrieved
131
+ if isinstance(snippet, dict)
132
+ ]
133
+
134
+ if citations:
135
+ lead = retrieved[0]
136
+ titles = ", ".join(
137
+ f"{c['title']} [{c['id']}]" for c in citations if c.get("title")
138
+ )
139
+ answer = (
140
+ f"For '{query}': the most relevant foundation reference is "
141
+ f"{lead.get('title')} [{lead.get('id')}]. "
142
+ f"Grounded on {len(citations)} foundation snippet(s): {titles}."
143
+ )
144
+ grounded = True
145
+ else:
146
+ answer = (
147
+ f"For '{query}': no foundation reference matched the query, so no "
148
+ "grounded answer could be produced from the demo corpus."
149
+ )
150
+ grounded = False
151
+
152
+ return {
153
+ "answer": answer,
154
+ "citations": citations,
155
+ "grounded": grounded,
156
+ }
157
+
158
+
159
+ # Capture the exact registry entries the decorators built at import time (with
160
+ # their full metadata + the live callables). ``load_demo_skills`` re-registers
161
+ # these same callables, so demo registration survives a ``registry.clear()``
162
+ # in a test fixture — Python's module cache would not re-fire the decorators.
163
+ _DEMO_ENTRIES = tuple(
164
+ entry
165
+ for entry in (
166
+ get_registry().resolve(RETRIEVE_SKILL_ID, DEMO_TASK_VERSION),
167
+ get_registry().resolve(SYNTHESIZE_SKILL_ID, DEMO_TASK_VERSION),
168
+ )
169
+ if entry is not None
170
+ )
171
+
172
+
173
+ def load_demo_skills() -> List[RegisteredSkill]:
174
+ """(Re)register the demo composite's tasks into the SDK registry.
175
+
176
+ Idempotent: the registry tolerates same-callable re-registration, so this
177
+ is safe to call after a ``registry.clear()`` (e.g. the ``fresh_registry``
178
+ test fixture) to restore the demo tasks. Kept separate from
179
+ :func:`plm_skill_kernel.registry.load_starter_skills` on purpose — demo
180
+ skills must never boot-load into a real kernel.
181
+ """
182
+ registry = get_registry()
183
+ for entry in _DEMO_ENTRIES:
184
+ registry.register(entry)
185
+ return list(_DEMO_ENTRIES)
186
+
187
+
188
+ __all__ = [
189
+ "DEMO_TASK_VERSION",
190
+ "RETRIEVE_SKILL_ID",
191
+ "SYNTHESIZE_SKILL_ID",
192
+ "demo_foundation_retrieve",
193
+ "demo_foundation_synthesize",
194
+ "load_demo_skills",
195
+ ]
@@ -23,6 +23,8 @@ from typing import Any, Dict, Optional, Tuple
23
23
 
24
24
  from pydantic import BaseModel, ConfigDict, Field
25
25
 
26
+ from ._demo_manifest import DemoManifestAttributes
27
+
26
28
 
27
29
  class TaskStep(BaseModel):
28
30
  """One ordered task inside a :class:`SkillPlan`.
@@ -102,6 +104,12 @@ class SkillPlan(BaseModel):
102
104
  # register + published profile, never here. Present-but-unenforced.
103
105
  certified: bool = False
104
106
  controlled: bool = False
107
+ # MOCK(MVP-DEMO): optional mirror of the ``manifest-profile/1.0``
108
+ # attributes, present so the demo skill "carries all manifest attributes"
109
+ # (FTR-2235 AC1). Every value is a fixture fake; the executor never reads
110
+ # it (AC4: no conformance gating). Backward-compatible default ``None`` so
111
+ # E1 plans and tests that omit it are unaffected.
112
+ manifest: Optional[DemoManifestAttributes] = None
105
113
 
106
114
  def task_ids(self) -> Tuple[str, ...]:
107
115
  return tuple(t.task_id for t in self.tasks)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plm-skill-kernel
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: TracePulse PLM Skill Kernel — Wave 2 Conv A. Decorator-based Skill SDK + V1 in-process dispatcher + Kernel HTTP server stub matching CR.10 §7.bis (POST /v1/skills/{id}/invoke). Ships 3 starter skills (cleansing.normalise, cleansing.dedupe, bpmn.generate). Sibling of plm-engine-core; the two communicate over the V1.1 HTTP loopback (SKILL_KERNEL_LOOPBACK=on) — no Python-level coupling at the dispatch boundary.
5
5
  Author: TracePulse
6
6
  License: Proprietary
@@ -23,6 +23,9 @@ plm_skill_kernel/conformance/models.py
23
23
  plm_skill_kernel/conformance/protocol.py
24
24
  plm_skill_kernel/executor/__init__.py
25
25
  plm_skill_kernel/executor/_catalog_fixture.py
26
+ plm_skill_kernel/executor/_demo_ids.py
27
+ plm_skill_kernel/executor/_demo_manifest.py
28
+ plm_skill_kernel/executor/_demo_skills.py
26
29
  plm_skill_kernel/executor/binding.py
27
30
  plm_skill_kernel/executor/executor.py
28
31
  plm_skill_kernel/executor/knowledge.py
@@ -65,6 +68,7 @@ plm_skill_kernel/skills_legacy/skill_orchestrator.py
65
68
  tests/test_agents_suggest_asgi.py
66
69
  tests/test_agents_suggest_unit.py
67
70
  tests/test_connector_read_mock.py
71
+ tests/test_demo_skill_package.py
68
72
  tests/test_dispatcher.py
69
73
  tests/test_documents_parse_asgi.py
70
74
  tests/test_documents_parse_unit.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "plm-skill-kernel"
7
- version = "1.3.0"
7
+ version = "1.4.0"
8
8
  description = "TracePulse PLM Skill Kernel — Wave 2 Conv A. Decorator-based Skill SDK + V1 in-process dispatcher + Kernel HTTP server stub matching CR.10 §7.bis (POST /v1/skills/{id}/invoke). Ships 3 starter skills (cleansing.normalise, cleansing.dedupe, bpmn.generate). Sibling of plm-engine-core; the two communicate over the V1.1 HTTP loopback (SKILL_KERNEL_LOOPBACK=on) — no Python-level coupling at the dispatch boundary."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -0,0 +1,261 @@
1
+ """Tests for the certified demo skill package (FTR-2235 / E4).
2
+
3
+ Covers the ratified acceptance criteria:
4
+
5
+ * **AC1** — the demo skill carries *all* manifest attributes: the fixture
6
+ manifest mirrors every ``manifest-profile/1.0`` machine key (28 total) and
7
+ is attached to the composite plan alongside the certified/controlled flags.
8
+ * **AC2** — the skill is marked certified/controlled via the inline-marked
9
+ fixture manifest AND registered in the catalog fixture.
10
+ * **AC3** — request → executor → ordered tasks → foundation knowledge →
11
+ grounded, cited result wires end-to-end, and the agent *delegates* to the
12
+ certified composite rather than answering itself.
13
+ * **AC4** — with the profile non-effective, the executor does NOT hard-gate on
14
+ a ``VALID`` conformance verdict: a plan with a full fixture manifest and one
15
+ with no manifest at all run identically.
16
+
17
+ Plus a **governance-firewall guard**: the faked ``certified`` maturity string
18
+ lives only in the demo fixture module and nowhere else under the package.
19
+
20
+ The demo tasks are quarantined out of ``load_starter_skills``; every test that
21
+ runs the composite loads them deliberately via ``load_demo_skills`` into a
22
+ cleared registry, exactly as the demo entrypoint does.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import os
27
+
28
+ import pytest
29
+
30
+ from plm_skill_kernel import SkillContext
31
+ from plm_skill_kernel.dispatcher import InProcessDispatcher
32
+ from plm_skill_kernel.executor import SkillExecutor, parse_request, resolve_binding, select_skill
33
+ from plm_skill_kernel.executor._catalog_fixture import (
34
+ DEMO_SKILL_ID,
35
+ DEMO_SKILL_VERSION,
36
+ build_demo_catalog,
37
+ )
38
+ from plm_skill_kernel.executor._demo_manifest import DEMO_MANIFEST, DemoManifestAttributes
39
+ from plm_skill_kernel.executor._demo_skills import (
40
+ demo_foundation_retrieve,
41
+ demo_foundation_synthesize,
42
+ load_demo_skills,
43
+ )
44
+ from plm_skill_kernel.mocks.foundation_seed import SeedFoundationKnowledgeProvider
45
+
46
+ # The 28 governed ``manifest-profile/1.0`` machine keys the fixture mirrors.
47
+ # Hardcoded (not derived from the model) so a drift in the model is caught
48
+ # against the profile's actual field inventory, not against itself.
49
+ _EXPECTED_MANIFEST_KEYS = frozenset(
50
+ {
51
+ "skill_id",
52
+ "name",
53
+ "mission",
54
+ "semantic_version",
55
+ "lifecycle_state",
56
+ "maturity_level",
57
+ "autonomy_ceiling",
58
+ "tenant_scope",
59
+ "audience",
60
+ "owner",
61
+ "minimum_trace_level",
62
+ "hitl_policy_reference",
63
+ "certification_reference",
64
+ "evaluation_reference",
65
+ "pinned_baseline_reference",
66
+ "contract_hash",
67
+ "input_schema_ref",
68
+ "output_schema_ref",
69
+ "preconditions",
70
+ "postconditions",
71
+ "failure_modes",
72
+ "evidence_expectations",
73
+ "kernel_api_compatibility",
74
+ "knowledge_api_compatibility",
75
+ "model_configuration",
76
+ "connector_deps",
77
+ "inference_deps",
78
+ "knowledge_deps",
79
+ }
80
+ )
81
+
82
+ # The faked maturity string that must be confined to the demo fixture module.
83
+ _FIREWALLED_MATURITY = "certified_industrial_capability"
84
+
85
+
86
+ @pytest.fixture(autouse=True)
87
+ def _enable_mocks(monkeypatch):
88
+ """Enable the mocks quarantine so the seed provider can be constructed."""
89
+ monkeypatch.setenv("PLM_MVP_MOCKS", "1")
90
+
91
+
92
+ def _ctx(plan) -> SkillContext:
93
+ return SkillContext(
94
+ skill_id=plan.skill_id,
95
+ skill_version=plan.version,
96
+ tenant_id="tenant-demo",
97
+ autonomy_level="L1",
98
+ )
99
+
100
+
101
+ # ── AC1: carries all manifest attributes ──────────────────────────────────
102
+ def test_manifest_mirrors_all_profile_keys():
103
+ assert set(DemoManifestAttributes.model_fields) == _EXPECTED_MANIFEST_KEYS
104
+ assert len(DemoManifestAttributes.model_fields) == 28
105
+
106
+
107
+ def test_manifest_certified_tier_values_populated():
108
+ # The "certified" story: the fixture claims certified maturity and fills
109
+ # the certified-tier governance references (all fakes, but present).
110
+ assert DEMO_MANIFEST.maturity_level == _FIREWALLED_MATURITY
111
+ assert DEMO_MANIFEST.lifecycle_state == "released"
112
+ assert DEMO_MANIFEST.autonomy_ceiling == "supervised"
113
+ assert DEMO_MANIFEST.certification_reference.startswith("mock://certification/")
114
+ assert DEMO_MANIFEST.evaluation_reference.startswith("mock://evaluation/")
115
+ assert DEMO_MANIFEST.pinned_baseline_reference.startswith("mock://baseline/")
116
+ assert DEMO_MANIFEST.skill_id == DEMO_SKILL_ID
117
+ assert DEMO_MANIFEST.semantic_version == DEMO_SKILL_VERSION
118
+
119
+
120
+ # ── AC2: marked certified/controlled + registered in the catalog ──────────
121
+ def test_catalog_plan_is_certified_controlled_with_manifest():
122
+ plan = resolve_binding(DEMO_SKILL_ID, catalog=build_demo_catalog())
123
+ assert plan.certified is True
124
+ assert plan.controlled is True
125
+ assert plan.manifest is DEMO_MANIFEST
126
+ # An ordered set of >= 2 tasks (AC1's "ordered set of tasks").
127
+ assert plan.task_ids() == ("retrieve", "synthesize")
128
+ assert len(plan.tasks) >= 2
129
+
130
+
131
+ # ── AC3: end-to-end request → tasks → knowledge → grounded result ─────────
132
+ async def test_end_to_end_agent_delegates_to_certified_composite(fresh_registry):
133
+ load_demo_skills() # deliberate opt-in (not boot-loaded)
134
+
135
+ request_text = "look up the foundation reference for the aluminium pump housing"
136
+
137
+ # The agent delegates: it routes to the certified composite skill id...
138
+ skill_id = select_skill(request_text)
139
+ assert skill_id == DEMO_SKILL_ID
140
+
141
+ # ...resolves it to an ordered task plan it did not itself author...
142
+ plan = resolve_binding(skill_id, catalog=build_demo_catalog())
143
+ assert plan.certified is True # delegating to a *certified* skill
144
+
145
+ result = await SkillExecutor(
146
+ dispatcher=InProcessDispatcher(),
147
+ knowledge=SeedFoundationKnowledgeProvider(top_k=8),
148
+ ).run(plan, parse_request(request_text), context=_ctx(plan))
149
+
150
+ assert result.ok is True
151
+ state = result.result
152
+
153
+ # The ordered tasks ran, and foundation knowledge was injected into task 1
154
+ # (retrieve) but not task 2 (synthesize) — proving per-task threading.
155
+ trace = state["_trace"]
156
+ assert [e["task_id"] for e in trace] == ["retrieve", "synthesize"]
157
+ retrieve_entry = next(e for e in trace if e["task_id"] == "retrieve")
158
+ synth_entry = next(e for e in trace if e["task_id"] == "synthesize")
159
+ assert retrieve_entry["knowledge_injected"] is True
160
+ assert synth_entry["knowledge_injected"] is False
161
+
162
+ # The result is grounded and cites foundation snippet ids (FND-*).
163
+ answer = state["result"]
164
+ assert answer["grounded"] is True
165
+ assert answer["citations"], "expected at least one citation"
166
+ cited_ids = [c["id"] for c in answer["citations"]]
167
+ assert all(cid.startswith("FND-") for cid in cited_ids)
168
+ # The aluminium housing record is the natural top hit for this query.
169
+ assert "FND-101" in cited_ids or "FND-201" in cited_ids
170
+ assert all(c["source"] == "foundation-seed (MOCK)" for c in answer["citations"])
171
+
172
+
173
+ # ── AC4: executor does NOT gate on a VALID conformance verdict ────────────
174
+ async def test_executor_does_not_gate_on_conformance(fresh_registry):
175
+ """A fully-manifested certified plan and a bare plan run identically.
176
+
177
+ The executor never consults ``certified`` / ``controlled`` / ``manifest``
178
+ (the profile is non-effective — AC4). Stripping all of it must not change
179
+ the outcome; if the executor gated on a VALID verdict, the bare plan would
180
+ diverge.
181
+ """
182
+ load_demo_skills()
183
+ request_text = "aluminium pump housing foundation reference"
184
+ catalog = build_demo_catalog()
185
+ certified_plan = resolve_binding(DEMO_SKILL_ID, catalog=catalog)
186
+
187
+ # Same ordered tasks, but no certification/manifest metadata at all.
188
+ bare_plan = certified_plan.model_copy(
189
+ update={"certified": False, "controlled": False, "manifest": None}
190
+ )
191
+
192
+ executor = SkillExecutor(
193
+ dispatcher=InProcessDispatcher(),
194
+ knowledge=SeedFoundationKnowledgeProvider(top_k=8),
195
+ )
196
+ certified_result = await executor.run(
197
+ certified_plan, parse_request(request_text), context=_ctx(certified_plan)
198
+ )
199
+ bare_result = await executor.run(
200
+ bare_plan, parse_request(request_text), context=_ctx(bare_plan)
201
+ )
202
+
203
+ assert certified_result.ok is True
204
+ assert bare_result.ok is True
205
+ # Identical grounded answer regardless of the (ignored) governance metadata.
206
+ assert certified_result.result["result"] == bare_result.result["result"]
207
+
208
+
209
+ # ── governance firewall: the faked maturity string is confined ────────────
210
+ def test_firewall_certified_string_confined_to_fixture_module():
211
+ import plm_skill_kernel
212
+
213
+ package_root = os.path.dirname(plm_skill_kernel.__file__)
214
+ offenders = []
215
+ for dirpath, _dirs, files in os.walk(package_root):
216
+ for fname in files:
217
+ if not fname.endswith(".py"):
218
+ continue
219
+ path = os.path.join(dirpath, fname)
220
+ with open(path, encoding="utf-8") as fh:
221
+ if _FIREWALLED_MATURITY in fh.read():
222
+ offenders.append(os.path.relpath(path, package_root))
223
+
224
+ assert offenders == [os.path.join("executor", "_demo_manifest.py")], (
225
+ "the faked certified maturity string must live ONLY in the demo "
226
+ f"fixture module; found in: {offenders}"
227
+ )
228
+
229
+
230
+ # ── optional unit tests for the two composite tasks ───────────────────────
231
+ async def test_retrieve_narrows_injected_foundation():
232
+ # Two snippets injected; only the aluminium one overlaps the query tokens.
233
+ foundation = [
234
+ {
235
+ "id": "FND-101",
236
+ "type": "material_spec",
237
+ "title": "Aluminium 6061-T6",
238
+ "snippet": "aluminium housing alloy",
239
+ "source": "foundation-seed (MOCK)",
240
+ },
241
+ {
242
+ "id": "FND-401",
243
+ "type": "unit_def",
244
+ "title": "Pressure units — bar and MPa",
245
+ "snippet": "pressure conversion",
246
+ "source": "foundation-seed (MOCK)",
247
+ },
248
+ ]
249
+ out = await demo_foundation_retrieve(
250
+ {"query": "aluminium housing", "foundation": foundation}, ctx=None
251
+ )
252
+ assert [s["id"] for s in out["retrieved"]] == ["FND-101"]
253
+ assert out["match_count"] == 1
254
+ assert out["considered"] == 2
255
+
256
+
257
+ async def test_synthesize_empty_retrieval_is_honest():
258
+ out = await demo_foundation_synthesize({"query": "nothing", "retrieved": []}, ctx=None)
259
+ assert out["grounded"] is False
260
+ assert out["citations"] == []
261
+ assert "no foundation reference matched" in out["answer"]
@@ -208,7 +208,7 @@ def test_resolve_binding_exact_version():
208
208
  plan = resolve_binding(DEMO_SKILL_ID, DEMO_SKILL_VERSION, catalog=catalog)
209
209
  assert plan.skill_id == DEMO_SKILL_ID
210
210
  assert len(plan.tasks) == 2 # ordered two-task demo skill
211
- assert plan.task_ids() == ("search", "get_record")
211
+ assert plan.task_ids() == ("retrieve", "synthesize")
212
212
 
213
213
 
214
214
  def test_resolve_binding_none_version_single():
@@ -1,80 +0,0 @@
1
- """Demo catalog fixture (FTR-2232 / E1).
2
-
3
- # MOCK(MVP-DEMO): this is a demo-scoped skill *catalog*, not the governed
4
- # skill registry or the ``manifest-profile/1.0`` authoring surface. It maps a
5
- # composite skill id to its ordered :class:`SkillPlan`. The concrete,
6
- # *certified* demo skill package is story FTR-2235 / E4 — it replaces /
7
- # extends this fixture with a data-dependent, human-signed-off plan. E1 ships
8
- # only a structurally-valid, resolvable entry so ``resolve_binding`` has
9
- # something to resolve and the seam is exercised end to end.
10
-
11
- The demo plan below composes two already-registered V1 knowledge skills into
12
- one ordered, two-task composite skill and threads a foundation-knowledge
13
- lookup into the first task — enough to demonstrate the executor mechanism.
14
- The ``certified`` / ``controlled`` flags are advisory demo values (present,
15
- unenforced); they must never be read as real ratifications.
16
- """
17
- from __future__ import annotations
18
-
19
- from typing import Dict, Tuple
20
-
21
- from .plan import SkillPlan, TaskStep
22
-
23
- # A demo corpus id understood by the in-process knowledge seed corpus.
24
- _DEMO_CORPUS = "foundation"
25
-
26
- DEMO_SKILL_ID = "demo.knowledge_lookup"
27
- DEMO_SKILL_VERSION = "0.1.0"
28
-
29
- _DEMO_PLAN = SkillPlan(
30
- skill_id=DEMO_SKILL_ID,
31
- version=DEMO_SKILL_VERSION,
32
- description=(
33
- "Demo composite skill: search the foundation corpus, then fetch a "
34
- "record — an ordered two-task skill the agent delegates to."
35
- ),
36
- # MOCK(MVP-DEMO): fixture governance flags — not real certification.
37
- certified=True,
38
- controlled=True,
39
- tasks=(
40
- TaskStep(
41
- task_id="search",
42
- skill_id="knowledge.search",
43
- version="1.0.0",
44
- description="Ordered task 1 — semantic search over the corpus.",
45
- static_payload={"corpus_id": _DEMO_CORPUS, "top_k": 5},
46
- input_keys=("query",),
47
- knowledge_query="foundation overview",
48
- output_key="search_result",
49
- ),
50
- TaskStep(
51
- task_id="get_record",
52
- skill_id="knowledge.get_record",
53
- version="1.0.0",
54
- description="Ordered task 2 — fetch a specific record by id.",
55
- static_payload={"corpus_id": _DEMO_CORPUS},
56
- input_keys=("record_id",),
57
- output_key="record_result",
58
- ),
59
- ),
60
- )
61
-
62
-
63
- # Catalog is keyed by (skill_id, version); a None-version lookup resolves to
64
- # the sole registered version for that id (demo convenience).
65
- DEMO_CATALOG: Dict[Tuple[str, str], SkillPlan] = {
66
- (DEMO_SKILL_ID, DEMO_SKILL_VERSION): _DEMO_PLAN,
67
- }
68
-
69
-
70
- def build_demo_catalog() -> Dict[Tuple[str, str], SkillPlan]:
71
- """Return a fresh copy of the demo catalog (callers may extend it)."""
72
- return dict(DEMO_CATALOG)
73
-
74
-
75
- __all__ = [
76
- "DEMO_CATALOG",
77
- "DEMO_SKILL_ID",
78
- "DEMO_SKILL_VERSION",
79
- "build_demo_catalog",
80
- ]