Skip to content

Commit 73832c7

Browse files
Restore source-agnostic artifact provenance
Assisted-by: GitHub Copilot (model: GPT-5.4, autonomous) Co-authored-by: nicolehaugen <10600161+nicolehaugen@users.noreply.github.com>
1 parent 2b8ce69 commit 73832c7

6 files changed

Lines changed: 15 additions & 31 deletions

File tree

‎src/specify_cli/_identifier.py‎

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""Deterministic identifiers for Spec Kit contributions and resolved stack layers.
22
33
Every command, template, script, and hook contribution surfaced by a preset or
4-
extension manifest carries a computed opaque ``id`` string, and every layer of a
5-
resolved artifact stack carries a matching ``lookupId``. The identifier value is
6-
derived only from author-declared manifest data — it never depends on file
4+
extension manifest carries a computed opaque ``id`` string, and provenance-backed
5+
layers of a resolved artifact stack carry a matching ``lookupId``. The identifier
6+
value is derived only from author-declared manifest data — it never depends on file
77
contents, timestamps, archive hashes, installation directory paths, install-time
88
random values, or list positions. That is what makes identifiers portable
99
across machines, project locations, and reinstalls, and what lets consumers use
@@ -23,9 +23,7 @@
2323
id = "{layer}:{sourceId}:hook:{eventName}:{command}"
2424
2525
Built-in artifacts have no public layer or lookup identifier. Their public
26-
identifier is source-agnostic: ``"{kind}:{name}"``. The pre-existing resolver
27-
still uses ``core:_:...`` lookup IDs internally; consumers that expose public
28-
artifact data translate those IDs at their boundary.
26+
identifier is source-agnostic: ``"{kind}:{name}"``.
2927
3028
The functions in this module are pure — inputs are strings or in-memory
3129
mappings parsed from a manifest, outputs are strings. None of them read from
@@ -51,7 +49,7 @@
5149
is the correct outcome for a layer with no originating manifest entry.
5250
"""
5351

54-
_LAYER_KINDS = frozenset({"core", PROJECT_OVERRIDE_LAYER, "preset", "extension"})
52+
_LAYER_KINDS = frozenset({PROJECT_OVERRIDE_LAYER, "preset", "extension"})
5553
_CONTRIBUTION_KINDS = frozenset({"command", "template", "script", "hook"})
5654
_NAMED_CONTRIBUTION_KINDS = _CONTRIBUTION_KINDS - {"hook"}
5755

@@ -122,8 +120,8 @@ def layer_kind_from_lookup_id(lookup_id: str) -> str | None:
122120
123121
``lookupId`` values on resolved stack layers follow the same
124122
``"{layer}:..."`` grammar as manifest-contribution ``id`` values (see
125-
module docstring), including ``core`` for built-in layers and
126-
:data:`PROJECT_OVERRIDE_LAYER` for project-local override layers.
123+
module docstring), including :data:`PROJECT_OVERRIDE_LAYER` for project-local
124+
override layers.
127125
This is the single place that knows the set of valid layer prefixes, so
128126
consumers can classify a lookupId without re-deriving the grammar via
129127
string-prefix checks of their own.

‎src/specify_cli/artifacts/__init__.py‎

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -236,18 +236,10 @@ def _public_layer_shape(
236236
) -> tuple[LayerName | None, str | None, str | None]:
237237
"""Translate resolver provenance into the public layer identity triple.
238238
239-
The resolver preserves its pre-existing built-in identity with
240-
``source == "core"`` and a ``core:_:`` lookup ID. Public artifact output
241-
omits that tier's identity while retaining preset, extension, and project
242-
override identities unchanged.
239+
Layers without a lookup identifier have no public provenance. Preset,
240+
extension, and project override identities are retained unchanged.
243241
"""
244242
lookup_id = resolver_layer.get("lookupId")
245-
if (
246-
resolver_layer.get("source") == "core"
247-
and isinstance(lookup_id, str)
248-
and lookup_id.startswith("core:_:")
249-
):
250-
return None, None, None
251243
if lookup_id is None:
252244
return None, None, None
253245
if not isinstance(lookup_id, str):

‎src/specify_cli/presets/__init__.py‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5784,9 +5784,6 @@ def _find_in_subdirs(base_dir: Path) -> Optional[Path]:
57845784
"path": core,
57855785
"source": "core",
57865786
"strategy": "replace",
5787-
"lookupId": derive_named_id(
5788-
"core", "_", template_type, template_name
5789-
),
57905787
})
57915788
else:
57925789
# Priority 5: Bundled core_pack (wheel install) or repo-root
@@ -5797,9 +5794,6 @@ def _find_in_subdirs(base_dir: Path) -> Optional[Path]:
57975794
"path": bundled,
57985795
"source": "core",
57995796
"strategy": "replace",
5800-
"lookupId": derive_named_id(
5801-
"core", "_", template_type, template_name
5802-
),
58035797
})
58045798

58055799
return layers

‎tests/test_artifact_command.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ def test_builtin_row_shape(self, spec_kit_project: Path):
474474
"speckit.constitution", "command"
475475
)[-1]
476476
assert resolver_layer["source"] == "core"
477-
assert resolver_layer["lookupId"] == "core:_:command:speckit.constitution"
477+
assert "lookupId" not in resolver_layer
478478

479479
info = ArtifactCatalog(spec_kit_project).get_artifact_info("speckit.constitution")
480480
assert info["id"] == "command:speckit.constitution"

‎tests/test_contribution_ids.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ class TestIdentifierDerivation:
122122
("project", "_", "command", "speckit.constitution", "project:_:command:speckit.constitution"),
123123
("project", "_", "template", "spec-template", "project:_:template:spec-template"),
124124
("project", "_", "script", "setup-plan", "project:_:script:setup-plan"),
125-
("core", "_", "command", "speckit.plan", "core:_:command:speckit.plan"),
126125
("preset", "speckit-core", "command", "speckit.plan", "preset:speckit-core:command:speckit.plan"),
127126
("preset", "speckit-core", "template", "spec-template", "preset:speckit-core:template:spec-template"),
128127
("preset", "speckit-core", "script", "setup-plan", "preset:speckit-core:script:setup-plan"),
@@ -157,6 +156,7 @@ def test_public_id_is_source_agnostic(self):
157156
[
158157
("preset", "speckit-core", "hook", "before_plan:speckit.plan"),
159158
("unknown", "source", "command", "speckit.plan"),
159+
("core", "_", "command", "speckit.plan"),
160160
],
161161
)
162162
def test_named_id_rejects_invalid_layer_or_kind(self, args):
@@ -179,7 +179,6 @@ class TestLayerKindFromLookupId:
179179
@pytest.mark.parametrize(
180180
"lookup_id, expected",
181181
[
182-
("core:_:command:speckit.plan", "core"),
183182
("preset:speckit-core:template:spec-template", "preset"),
184183
("extension:speckit-git:script:post-commit", "extension"),
185184
(f"{PROJECT_OVERRIDE_LAYER}:_:template:spec-template", PROJECT_OVERRIDE_LAYER),
@@ -197,6 +196,7 @@ def test_recognized_layer_prefixes(self, lookup_id, expected):
197196
[
198197
"",
199198
"bogus:_:command:speckit.plan",
199+
"core:_:command:speckit.plan",
200200
"core",
201201
":_:command:speckit.plan",
202202
"core:not-an-id",
@@ -385,7 +385,7 @@ def test_builtin_layer_preserves_resolver_provenance(self, tmp_path):
385385
resolver.templates_dir = project / "templates"
386386
layers = resolver.collect_all_layers("spec-template", "template")
387387
builtin_layer = next(layer for layer in layers if layer["source"] == "core")
388-
assert builtin_layer["lookupId"] == "core:_:template:spec-template"
388+
assert "lookupId" not in builtin_layer
389389

390390
def test_preset_layer_lookup_id_matches_manifest_contribution_id(self, tmp_path):
391391
project = _make_project(tmp_path)

‎tests/test_presets.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,7 +1707,7 @@ def test_collect_all_layers_finds_bundled_core_without_specify_commands(
17071707
layers = resolver.collect_all_layers("speckit.implement", "command")
17081708
assert layers, "expected a bundled core base layer to be found"
17091709
assert layers[-1]["source"] == "core"
1710-
assert layers[-1]["lookupId"] == "core:_:command:speckit.implement"
1710+
assert "lookupId" not in layers[-1]
17111711
assert layers[-1]["path"].parts[-2:] == ("commands", "implement.md")
17121712

17131713
def test_resolve_command_falls_back_to_bundled_core(self, project_dir):
@@ -12883,7 +12883,7 @@ def test_single_core_layer(self, project_dir):
1288312883
layers = resolver.collect_all_layers("spec-template")
1288412884
assert len(layers) == 1
1288512885
assert layers[0]["source"] == "core"
12886-
assert layers[0]["lookupId"] == "core:_:template:spec-template"
12886+
assert "lookupId" not in layers[0]
1288712887
assert layers[0]["strategy"] == "replace"
1288812888

1288912889
def test_layers_include_presets(self, project_dir, temp_dir, valid_pack_data):

0 commit comments

Comments
 (0)