Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .apm/architecture/owners/contracts-tooling.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,15 @@
},
{
"id": "read-only-lockfile-path",
"decision": "Read-only lockfile path resolution",
"owner": "deps/lockfile.py (resolve_lockfile_path_for_read)",
"decision": "Lockfile read path, timestamp emission, and reproducible fallback",
"owner": "deps/lockfile.py (resolve_lockfile_path_for_read, LockFile.write, resolve_reproducible_timestamp)",
"selectors": ["src/apm_cli/deps/lockfile.py"],
"guards": ["contracts-tooling-lockfile-read"]
"guards": [
"contracts-tooling-lockfile-read",
"contracts-tooling-lockfile-timestamp",
"contracts-tooling-lockfile-timestamp-constructor",
"contracts-tooling-lockfile-timestamp-fallback"
]
},
{
"id": "generated-content-footer-wording",
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Generated bundle and plugin metadata now uses deterministic LF line endings,
keeping generated metadata byte-stable across operating systems.
(by @WilliamK112, #2638)
- Newly generated `apm.lock.yaml` files no longer include volatile
`generated_at` metadata, preventing timestamp-only merge conflicts. Existing
lockfiles preserve the legacy field on no-op writes and refresh it on
substantive writes; deleting it once prevents APM from restoring it.
`openapm-v0.1.md` requirement `req-lk-005`
defines these omission and opt-in semantics. Agent Plugin archive timestamps
remain byte-reproducible without the field by using `SOURCE_DATE_EPOCH` or a
fixed epoch. (by @lachieh; closes #2572) (#2616)
- Lockfiles generated on Windows for marketplace-plugin / skill-subset git
dependencies now pass `apm install --frozen` on Linux, and vice versa, by
hashing synthetic manifests with deterministic LF line endings.
Expand Down
5 changes: 3 additions & 2 deletions CONFORMANCE.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,10 @@
"keyword": "MUST",
"section": "5.5",
"status": "active",
"test_count": 1,
"test_count": 2,
"tests": [
"tests/spec_conformance/test_lockfile_reqs.py::test_lockfile_dependency_carries_resolved_field"
"tests/spec_conformance/test_lockfile_reqs.py::test_lockfile_dependency_carries_resolved_field",
"tests/spec_conformance/test_lockfile_reqs.py::test_new_lockfile_omits_generated_at_by_default"
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion CONFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ All four conformance classes (Producer, Consumer, Registry, Governance) carry ac
| [req-lk-002](docs/src/content/docs/specs/openapm-v0.1.md#req-lk-002) | MUST | 5.4 | consumer | active | 1 | - |
| [req-lk-003](docs/src/content/docs/specs/openapm-v0.1.md#req-lk-003) | MUST | 5.2 | consumer | active | 2 | - |
| [req-lk-004](docs/src/content/docs/specs/openapm-v0.1.md#req-lk-004) | MUST | 5.4 | consumer | active | 1 | - |
| [req-lk-005](docs/src/content/docs/specs/openapm-v0.1.md#req-lk-005) | MUST | 5.5 | consumer | active | 1 | - |
| [req-lk-005](docs/src/content/docs/specs/openapm-v0.1.md#req-lk-005) | MUST | 5.5 | consumer | active | 2 | - |
| [req-lk-006](docs/src/content/docs/specs/openapm-v0.1.md#req-lk-006) | MUST | 5.5 | consumer | active | 1 | - |
| [req-lk-007](docs/src/content/docs/specs/openapm-v0.1.md#req-lk-007) | SHOULD | 5.5 | consumer | active | 1 | - |
| [req-lk-008](docs/src/content/docs/specs/openapm-v0.1.md#req-lk-008) | MUST | 5.6 | consumer | active | 1 | - |
Expand Down
1 change: 0 additions & 1 deletion apm.lock.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
lockfile_version: '1'
generated_at: '2026-08-31T13:08:37.404349+00:00'
apm_version: 0.29.0
dependencies:
- repo_url: _local/apm-issue-autopilot
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ requirements:
keyword: MUST
section: "5.5"
conformance_class: consumer
notes: "generated_at is optional advisory metadata; consumers omit it from new lockfiles by default and preserve an existing omission unless explicitly configured otherwise"
- id: req-lk-006
keyword: MUST
section: "5.5"
Expand Down
21 changes: 10 additions & 11 deletions docs/src/content/docs/concepts/package-anatomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ by `apm install`; commit it.

```yaml
lockfile_version: '1'
generated_at: '2026-04-21T21:45:34.516938+00:00'
apm_version: 0.22.0

dependencies:
Expand Down Expand Up @@ -199,16 +198,16 @@ local_deployed_file_hashes:

Top-level fields:

| Field | Notes |
|--------------------------------|------------------------------------------------|
| `lockfile_version` | Schema version of the lockfile. |
| `generated_at` | ISO timestamp of last write. |
| `apm_version` | CLI version that generated the file. |
| `dependencies` | List of `LockedDependency` entries. |
| `mcp_servers` | Resolved MCP server identifiers. |
| `mcp_configs` | Per-harness MCP configuration blobs. |
| `local_deployed_files` | Files this package wrote to deployed dirs. |
| `local_deployed_file_hashes` | SHA-256 of each local-deployed file. |
| Field | Notes |
|--------------------------------|-----------------------------------------------------------|
| `lockfile_version` | Schema version of the lockfile. |
| `apm_version` | CLI version that generated the file. |
| `dependencies` | List of `LockedDependency` entries. |
| `mcp_servers` | Resolved MCP server identifiers. |
| `mcp_configs` | Per-harness MCP configuration blobs. |
| `local_deployed_files` | Files this package wrote to deployed dirs. |
| `local_deployed_file_hashes` | SHA-256 of each local-deployed file. |
| _(Deprecated)_ `generated_at` | Write timestamp. Remove from lockfile to avoid conflicts. |

Each dependency stores canonical identity and resolution data. For
case-insensitive providers, `repo_url` is the canonical comparison value while
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/reference/cli/lock.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ apm lock export [OPTIONS]
| `--format FORMAT`, `-f FORMAT` | `cyclonedx` | SBOM output format: `cyclonedx` (1.5) or `spdx` (2.3). |
| `--output FILE`, `-o FILE` | stdout | Write the SBOM to a file instead of stdout. |
| `--global`, `-g` | off | Read the user-scope (`~/.apm/`) lockfile instead of the current project. |
| `--timestamp TS` | auto | Pin the SBOM timestamp for reproducible output. The value must be ISO 8601 with a timezone (e.g. `2024-06-01T00:00:00+00:00`); malformed or timezone-naive values fail. Defaults to `SOURCE_DATE_EPOCH`, then the lockfile's `generated_at`. |
| `--timestamp TS` | auto | Pin the SBOM timestamp for reproducible output. The value must be ISO 8601 with a timezone (e.g. `2024-06-01T00:00:00+00:00`); malformed or timezone-naive values fail. Defaults to `SOURCE_DATE_EPOCH`, then the lockfile's legacy `generated_at`, then the Unix epoch. |

Component identity is a Package URL (`pkg:github/<owner>/<repo>@<commit>` for git deps, `pkg:oci/<name>@<digest>` for registry deps, `pkg:generic/<name>@<content_hash>` for local primitives), and the declared license is passed through verbatim (or `NOASSERTION` when undeclared). Output is deterministic -- components sorted by purl with a pinned timestamp -- so two runs are byte-identical. Credentials in recorded URLs are scrubbed. Diagnostics and update notifications route to stderr from process startup, so `apm lock export | jq` stays clean. See [Inventory export (SBOM)](../../../enterprise/security/#inventory-export-sbom) for the full model.

Expand Down
1 change: 0 additions & 1 deletion docs/src/content/docs/reference/cli/pack.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ pack:
bundle_files:
.github/agents/architect.md: a1b2c3...
lockfile_version: '1'
generated_at: ...
dependencies:
- repo_url: owner/repo
```
Expand Down
10 changes: 6 additions & 4 deletions docs/src/content/docs/reference/lockfile-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ on any machine.

```yaml
lockfile_version: "1"
generated_at: "2026-05-10T20:14:00+00:00"
apm_version: "0.6.4"
dependencies:
- repo_url: https://github.com/acme-corp/security-baseline
Expand Down Expand Up @@ -135,7 +134,7 @@ deployments:
| Field | Type | Required | Notes |
|---|---|---|---|
| `lockfile_version` | string | yes | Schema version. `"1"` for plain Git projects; `"2"` when any dependency has `source: "registry"` or Git semver resolution fields (`constraint`, `resolved_tag`, `resolved_at`). |
| `generated_at` | ISO 8601 string | yes | UTC timestamp of the last write. Ignored by equivalence checks. |
| _(Deprecated)_ `generated_at` | ISO 8601 string | no | Legacy write timestamp. New lockfiles omit it; when an existing lockfile carries it, APM refreshes it on substantive writes. Ignored by equivalence checks. |
| `apm_version` | string | no | APM CLI version that wrote the file. Diagnostic only. |
| `dependencies` | list | yes | Resolved APM packages. See [per-entry fields](#per-entry-fields). |
| `mcp_servers` | list of strings | no | Names of MCP servers managed as of the last install or update, including transitively contributed servers. |
Expand Down Expand Up @@ -329,7 +328,11 @@ shipped.

`apm install` only rewrites the file when its semantic content changes
(`generated_at` and `apm_version` are ignored when comparing). A no-op install
leaves the file untouched.
leaves the file untouched. New lockfiles omit `generated_at` so independent
dependency changes do not manufacture timestamp conflicts. If a pre-existing
lockfile includes the field, APM retains it for compatibility and refreshes it
only on a substantive write. To migrate a legacy lockfile manually, delete the
`generated_at: ...` line from `apm.lock.yaml` once; APM will not add it back.

## Drift and integrity

Expand Down Expand Up @@ -389,7 +392,6 @@ local skill:

```yaml
lockfile_version: "1"
generated_at: "2026-05-10T20:14:00+00:00"
apm_version: "0.6.4"
dependencies:
- repo_url: github.com/octocat/example-skills
Expand Down
25 changes: 12 additions & 13 deletions docs/src/content/docs/specs/openapm-v0.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,6 @@ Example (informative, minimal):

```yaml
lockfile_version: "1"
generated_at: "2026-05-10T20:14:00+00:00"
apm_version: "0.6.4"
dependencies:
- repo_url: github.com/octocat/example
Expand Down Expand Up @@ -1141,16 +1140,17 @@ against.
<a id="req-lk-005"></a>
**[req-lk-005]** A conforming **consumer** implementation MUST treat
two lockfiles as semantically equivalent if they differ only in the
values of `generated_at` and `apm_version`. A no-op install
operation MUST NOT rewrite a lockfile whose only changed fields
would be these two. Consumers operating in privacy-sensitive
deployments MAY omit `generated_at` and `apm_version` entirely;
their absence MUST NOT affect content-equivalence comparison.
Consumers SHOULD expose a `--no-provenance` (or equivalent) flag
that suppresses these fields on write. Consumers SHOULD NOT include
`generated_at` or `apm_version` in lockfiles persisted by
deployments that have declared privacy sensitivity. When a
consumer writes a lockfile, the `dependencies` list MUST be
presence or values of `generated_at` and `apm_version`. A no-op
install operation MUST NOT rewrite a lockfile whose only changed
fields would be these two. `generated_at` is optional, advisory
metadata. Consumers MUST omit `generated_at` from newly created
lockfiles unless explicit user or deployment configuration requests
it. When an existing lockfile omits `generated_at`, a consumer MUST
NOT reintroduce it solely as metadata during a later write unless
that configuration opts in. Consumers operating in privacy-sensitive
deployments SHOULD omit both provenance fields to avoid leaking tool
version or build-time information. When a consumer writes a
lockfile, the `dependencies` list MUST be
ordered ascending lexicographically by the tuple (`repo_url`,
`virtual_path`); entries without `virtual_path` sort as if
`virtual_path` were the empty string. Two lockfiles differing
Expand Down Expand Up @@ -2213,7 +2213,6 @@ resolves to the lockfile:

```yaml
lockfile_version: "2"
generated_at: "2026-05-10T20:14:00+00:00"
apm_version: "0.7.0"
dependencies:
- repo_url: github.com/contoso/security-baseline
Expand Down Expand Up @@ -3404,7 +3403,6 @@ A Consumer reading the manifest above produces the lockfile:

```yaml
lockfile_version: "2"
generated_at: "2026-05-10T20:14:00+00:00"
apm_version: "0.7.0"
dependencies:
- repo_url: github.com/contoso/common-prompts
Expand Down Expand Up @@ -3850,6 +3848,7 @@ renumbering of conformance classes.
| 0.1.35 | 2026-08-27 | Stale-spec (Mode C) amendment recording a machine-verifiable native Agent Plugins lifecycle. Added [req-tg-013] (Section 8.5.7, consumer MUST): schema, effective-target, integrity, security, and executable admission drives one aggregate direct-plus-transitive registration per scope without locating, invoking, or version-checking a host binary during lifecycle operations; packages remain materialized in place and opaque to legacy projection; direct dependencies win plugin-name collisions over transitive dependencies, same-precedence collisions fail, and recorded ownership does not silently repoint to a transitive claimant; a consumer-owned marketplace identifier and activation suffix are reserved only with the exact generated directory-marketplace entry; the ownership record is primary evidence, while missing-record recovery may re-adopt only that exact entry and reconcile the reserved namespace; foreign collisions and invalid JSON fail closed; unrelated JSON values are preserved semantically though stable serialization may reformat them; and catalog, ownership-record, and settings writes form one rollback unit. Revised [req-tg-011] to clarify that acquisition, materialization, and lock recording may precede target exclusion, which creates no target registration or primitive projection and does not block ordinary dependencies in the same batch. Compatibility is qualified at release or build time by the pinned real-host lifecycle suite; runtime availability is the operator's responsibility. Added the native plugin namespace and ownership-recovery threat to Section 10. Section 8.7, Section 11.3.2 Consumer enumeration, Appendix C, and conformance coverage updated. Statement count: 118 -> 119 (114 MUST, 5 SHOULD). |
| 0.1.36 | 2026-08-29 | Editorial and defensive alignment for [req-tg-011] and [req-tg-013]. Named the [req-tg-008] result as the effective target intersection; scoped aggregate registration and plugin-name claimant selection to dependencies that passed admission; required target contraction to retire consumer-owned native registration; required advisory uninstall, prune, and restore reconciliation to omit ambiguous or changed-owner plugin entries without blocking cleanup; restored exact removal boundaries; defined directory-marketplace entries; and added reserved namespace disclosure to Section 11.2. Added conformance coverage for direct-owner promotion, advisory collision cleanup, and transitive owner-repoint refusal. Statement count remains 119 (114 MUST, 5 SHOULD). |
| 0.1.37 | 2026-09-01 | Spec-citation fold for safe full-SHA revision-pin updates (closes #2511 Mode-B silent-extension gate). Added [req-rs-017] (Section 7.7, consumer MUST): a consumer extension may replace a full commit pin only with the peeled commit of the highest eligible non-prerelease annotated tag, including 0.x; no eligible tag retains the current commit and allows unrelated updates to continue; malformed, ambiguous, or failed remote tag resolution stops before manifest or lockfile writes. Revised [req-rs-011], [req-rs-012], and [req-rs-015] for bounded manifest rewrite, scoped operation, advisory tag provenance, and network-free replay. Section 5.2, Section 5.6, Section 7.11, Section 11.3.2, Appendix C, and conformance coverage updated. Statement count: 119 -> 120 (115 MUST, 5 SHOULD). |
| 0.1.38 | 2026-09-01 | Defensive amendment of [req-lk-005] (no new normative statement; count remains 120 (115 MUST, 5 SHOULD)): `generated_at` is optional advisory metadata, new lockfiles omit it by default, and later writes preserve an existing omission unless explicitly configured otherwise. |

Errata (none at publication).

Expand Down
Loading