plm-skill-kernel 1.2.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.2.0 → plm_skill_kernel-1.4.0}/PKG-INFO +2 -1
  2. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/__init__.py +1 -1
  3. {plm_skill_kernel-1.2.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.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/plan.py +8 -0
  9. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/mocks/__init__.py +22 -1
  10. plm_skill_kernel-1.4.0/plm_skill_kernel/mocks/connectors.py +182 -0
  11. plm_skill_kernel-1.4.0/plm_skill_kernel/mocks/engine_core_stub.py +136 -0
  12. plm_skill_kernel-1.4.0/plm_skill_kernel/mocks/knowledge_services_port.py +359 -0
  13. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/PKG-INFO +2 -1
  14. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/SOURCES.txt +10 -0
  15. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/requires.txt +1 -0
  16. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/pyproject.toml +10 -1
  17. plm_skill_kernel-1.4.0/tests/test_connector_read_mock.py +128 -0
  18. plm_skill_kernel-1.4.0/tests/test_demo_skill_package.py +261 -0
  19. plm_skill_kernel-1.4.0/tests/test_engine_core_stub_mock.py +141 -0
  20. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_executor.py +1 -1
  21. plm_skill_kernel-1.4.0/tests/test_knowledge_services_port_mock.py +176 -0
  22. plm_skill_kernel-1.2.0/plm_skill_kernel/executor/_catalog_fixture.py +0 -80
  23. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/README.md +0 -0
  24. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/__main__.py +0 -0
  25. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/__init__.py +0 -0
  26. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_cli.py +0 -0
  27. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_output_safety.py +0 -0
  28. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_serialize.py +0 -0
  29. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_terminal.py +0 -0
  30. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_verdict.py +0 -0
  31. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/_version.py +0 -0
  32. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/exit_codes.py +0 -0
  33. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/models.py +0 -0
  34. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/conformance/protocol.py +0 -0
  35. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/dispatcher.py +0 -0
  36. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/binding.py +0 -0
  37. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/executor.py +0 -0
  38. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/knowledge.py +0 -0
  39. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/request.py +0 -0
  40. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/executor/selector.py +0 -0
  41. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/mocks/foundation_seed.py +0 -0
  42. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/registry.py +0 -0
  43. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/sdk/__init__.py +0 -0
  44. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/sdk/_registry.py +0 -0
  45. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/sdk/context.py +0 -0
  46. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/sdk/decorator.py +0 -0
  47. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/sdk/types.py +0 -0
  48. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/server.py +0 -0
  49. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/__init__.py +0 -0
  50. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/agents/__init__.py +0 -0
  51. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/agents/suggest.py +0 -0
  52. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/bpmn/__init__.py +0 -0
  53. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/bpmn/generate.py +0 -0
  54. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/cleansing/__init__.py +0 -0
  55. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/cleansing/dedupe.py +0 -0
  56. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/cleansing/normalise.py +0 -0
  57. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/documents/__init__.py +0 -0
  58. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/documents/parse.py +0 -0
  59. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/__init__.py +0 -0
  60. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/_v1_corpus.py +0 -0
  61. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/get_record.py +0 -0
  62. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/resolve.py +0 -0
  63. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/retrieve.py +0 -0
  64. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/search.py +0 -0
  65. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills/knowledge/validate.py +0 -0
  66. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/__init__.py +0 -0
  67. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/knowledge_pack_loader.py +0 -0
  68. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/knowledge_search_engine.py +0 -0
  69. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/plm_skill_registry.py +0 -0
  70. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/plm_tool_definitions.py +0 -0
  71. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel/skills_legacy/skill_orchestrator.py +0 -0
  72. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/dependency_links.txt +0 -0
  73. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/entry_points.txt +0 -0
  74. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/plm_skill_kernel.egg-info/top_level.txt +0 -0
  75. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/setup.cfg +0 -0
  76. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_agents_suggest_asgi.py +0 -0
  77. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_agents_suggest_unit.py +0 -0
  78. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_dispatcher.py +0 -0
  79. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_documents_parse_asgi.py +0 -0
  80. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_documents_parse_unit.py +0 -0
  81. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_foundation_mocks.py +0 -0
  82. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_sdk_decorator.py +0 -0
  83. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_server.py +0 -0
  84. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_skills_cleansing.py +0 -0
  85. {plm_skill_kernel-1.2.0 → plm_skill_kernel-1.4.0}/tests/test_skills_knowledge.py +0 -0
  86. {plm_skill_kernel-1.2.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.2.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
@@ -13,6 +13,7 @@ Requires-Dist: uvicorn>=0.27
13
13
  Requires-Dist: httpx>=0.27.0
14
14
  Requires-Dist: plm-engine-core
15
15
  Requires-Dist: plm-knowledge
16
+ Requires-Dist: plm-connector-sdk<2,>=1.1
16
17
  Provides-Extra: test
17
18
  Requires-Dist: pytest; extra == "test"
18
19
  Requires-Dist: pytest-asyncio; extra == "test"
@@ -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.2.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)
@@ -50,4 +50,25 @@ def require_mocks(what: str = "this mock") -> None:
50
50
  )
51
51
 
52
52
 
53
- __all__ = ["MOCKS_ENV", "MocksDisabled", "mocks_enabled", "require_mocks"]
53
+ # ── governed contract package versions this mocks layer targets (AC4) ──────
54
+ # The MVP mocks stand in for three governed, separately-versioned packages. This
55
+ # dict is the single authoritative record of the exact versions the mocks were
56
+ # written against; ``tests/contracts/test_mvp_mocks_boundary.py`` asserts each
57
+ # against what is actually installed. A governed-package bump therefore surfaces
58
+ # as a failing pin that a human re-syncs on purpose (updating MOCKS.md), never a
59
+ # silent absorption. Per-mock *shape* pins (KS_CONTRACT_VERSION,
60
+ # CONNECTOR_SDK_VERSION, SELECTION_REQUIRED_KEYS) live in their own modules.
61
+ GOVERNED_CONTRACT_VERSIONS = {
62
+ "plm-shared": "1.2.1", # CR.10/CR.12 wire shapes the SDK + dispatcher consume
63
+ "plm-connector-sdk": "1.3.1", # ConnectorReadResult — AC2 reaches a connector through it
64
+ "plm-engine-core": "1.6.0", # runtime.llm_service — the AC3 stub stands in for it
65
+ }
66
+
67
+
68
+ __all__ = [
69
+ "GOVERNED_CONTRACT_VERSIONS",
70
+ "MOCKS_ENV",
71
+ "MocksDisabled",
72
+ "mocks_enabled",
73
+ "require_mocks",
74
+ ]