Skip to content

fix(lockfile): omit generated_at from new lockfiles - #2616

Open
Lachlan Heywood (lachieh) wants to merge 11 commits into
microsoft:mainfrom
lachieh:fix/2572-conditional-generated-at
Open

fix(lockfile): omit generated_at from new lockfiles#2616
Lachlan Heywood (lachieh) wants to merge 11 commits into
microsoft:mainfrom
lachieh:fix/2572-conditional-generated-at

Conversation

@lachieh

@lachieh Lachlan Heywood (lachieh) commented Aug 18, 2026

Copy link
Copy Markdown

fix(lockfile): omit generated_at from new lockfiles

TL;DR

New apm.lock.yaml files no longer include the volatile generated_at field, preventing unrelated dependency changes from manufacturing a merge conflict. Existing lockfiles that already contain the field keep their legacy behavior: no-op writes preserve it and substantive writes refresh it.

Note

Removing generated_at once opts an existing lockfile into timestamp-free output; later writes will not add it back.

Problem (WHY)

  • Every newly generated lockfile carried a wall-clock value even though the value does not participate in dependency resolution or semantic equality.
  • Independent branches that changed different dependency content could still conflict on the one timestamp line. The issue reports that "The generated_at key is consistently causing merge conflicts."
  • [!] apm lock export used the legacy field as one timestamp fallback, so timestamp-free lockfiles need a deterministic final fallback.

Why these matter: the expected behavior is explicit--"Merging unrelated changes should not cause merge conflicts". The fix must also preserve compatibility for repositories that already carry the field and deterministic SBOM output for repositories that do not.

Approach (WHAT)

  • Make LockFile.generated_at optional and omit it during serialization when absent.
  • Let the existing on-disk lockfile select compatibility behavior: preserve or refresh a legacy timestamp only when that file already has one.
  • Remove the separate MCP timestamp assignment so all write paths share the same LockFile.write() policy.
  • Retain deterministic SBOM export by falling back to the Unix epoch after SOURCE_DATE_EPOCH and a legacy lockfile timestamp.
  • Update user-facing examples, reference documentation, changelog text, and tests to describe both new and legacy behavior.

Implementation (HOW)

File Intent
src/apm_cli/deps/lockfile.py Makes generated_at optional, conditionally serializes it, and centralizes legacy preservation/refresh behavior in LockFile.write().
src/apm_cli/integration/mcp_integrator.py Removes MCP's unconditional timestamp assignment so new MCP-created lockfiles remain timestamp-free.
src/apm_cli/commands/lock.py Documents the deterministic Unix-epoch fallback used by timestamp-free SBOM exports.
CHANGELOG.md Records the bug fix and compatibility behavior in the Unreleased section.
docs/src/content/docs/concepts/package-anatomy.md Removes the timestamp from the canonical example and marks the field as deprecated compatibility metadata.
docs/src/content/docs/reference/cli/lock.md Extends the SBOM timestamp fallback order through the Unix epoch.
docs/src/content/docs/reference/cli/pack.md Removes the timestamp from generated lockfile examples.
docs/src/content/docs/reference/lockfile-spec.md Makes the field optional and documents preservation, refresh, and opt-out semantics.
packages/apm-guide/.apm/skills/apm-usage/commands.md Keeps the bundled command reference aligned with the CLI behavior.
tests/test_lockfile.py Covers timestamp-free serialization and legacy timestamp refresh on a substantive write.
tests/unit/commands/test_lock_export_command.py Covers fixed-epoch SBOM export when the lockfile has no timestamp.
tests/unit/install/test_mcp_lockfile_determinism.py Updates new-lock expectations and covers legacy refresh through the install/MCP persistence path.
tests/unit/integration/test_mcp_integrator.py Verifies a missing MCP lockfile is created without generated_at.
tests/integration/test_install_lsp_lockfile_determinism.py Verifies timestamp-free LSP installs remain byte-stable.
tests/integration/test_install_mcp_lockfile_determinism.py Verifies new MCP lockfiles stay timestamp-free across no-op and substantive target changes.
tests/integration/test_cache_lockfile_parity.py Clarifies that parity normalization applies only to optional legacy metadata.
tests/integration/test_config_surface_lifecycle_contract.py Aligns lifecycle-contract terminology with the optional legacy field.
tests/integration/test_oci_mcp_lifecycle_contract.py Aligns OCI/MCP idempotency terminology with the optional legacy field.

Diagrams

Legend: dashed nodes are the changed timestamp decisions; first check whether the on-disk lockfile opted into legacy metadata, then preserve or refresh it.

flowchart LR
    N["New LockFile"] --> W["LockFile.write"]
    E["Existing apm.lock.yaml"] --> R["LockFile.read"]
    R --> W
    W --> H{"Existing generated_at?"}
    H -->|No| O["to_yaml omits generated_at"]
    H -->|Yes| S{"Semantic content changed?"}
    S -->|No| P["Preserve existing timestamp"]
    S -->|Yes| U["Refresh UTC timestamp"]
    P --> I["to_yaml includes generated_at"]
    U --> I
    classDef new stroke-dasharray: 5 5;
    class H,O,P,U new;
Loading

Trade-offs

  • Disk presence selects compatibility. Chose the existing field as the opt-in signal instead of adding a new CLI flag or schema version; this keeps old repositories stable while making new repositories conflict-free.
  • Legacy metadata remains writable. Chose to refresh timestamps on substantive writes instead of freezing stale metadata forever; removing the field remains the explicit migration to timestamp-free output.
  • Unix epoch over current time for SBOM fallback. Chose deterministic output instead of reintroducing wall-clock variability in exported inventory.
  • Semantic equality is unchanged. Kept generated_at excluded from equivalence checks; the field remains metadata rather than dependency state.

Benefits

  1. A newly generated lockfile has zero volatile timestamp lines available to conflict across otherwise mergeable branches.
  2. Existing timestamp-bearing lockfiles continue to round-trip and record a fresh timestamp on substantive writes.
  3. MCP and LSP lifecycle paths converge on the same timestamp-free default.
  4. SBOM export remains byte-deterministic even when neither an environment timestamp nor legacy lockfile timestamp exists.

Validation

uv run --frozen --extra dev ruff check src/ tests/:

All checks passed!

uv run --frozen --extra dev ruff format --check src/ tests/:

1615 files already formatted
Focused unit results (124 tests)

uv run --frozen --extra dev pytest -p no:cacheprovider -q tests/test_lockfile.py tests/unit/commands/test_lock_export_command.py tests/unit/install/test_mcp_lockfile_determinism.py tests/unit/integration/test_mcp_integrator.py::TestUpdateLockfile:

........................................................................ [ 58%]
....................................................                     [100%]
124 passed in 0.58s
Affected integration results (26 passed, 8 skipped)

uv run --frozen --extra dev pytest -p no:cacheprovider -q --tb=short tests/integration/test_cache_lockfile_parity.py tests/integration/test_config_surface_lifecycle_contract.py tests/integration/test_install_lsp_lockfile_determinism.py tests/integration/test_install_mcp_lockfile_determinism.py tests/integration/test_oci_mcp_lifecycle_contract.py:

..........................ssssssss                                       [100%]
26 passed, 8 skipped in 20.63s

The remaining lint guards, duplication check, and architecture/auth boundary
scripts completed with exit code 0. Earlier broad validation also completed
with 19,940 unit tests passing and 159 specification tests passing.

Scenario Evidence

# Scenario (user promise) Principle(s) Test(s) proving it Type
1 Generate or update a new lockfile without creating a timestamp-only conflict Governed by policy, DevX tests/test_lockfile.py::TestLockFile::test_to_yaml
tests/integration/test_install_mcp_lockfile_determinism.py::test_installed_mcp_lifecycle_is_no_write_until_real_target_change (regression-trap for #2572)
unit, integration
2 Update a legacy lockfile and keep its timestamp current without changing no-op semantics Governed by policy, OSS / community-driven tests/test_lockfile.py::TestLockFile::test_write_refreshes_existing_generated_at
tests/unit/install/test_mcp_lockfile_determinism.py::test_changed_mcp_dependencies_refresh_legacy_generated_at
unit
3 Export the same SBOM bytes when a lockfile has no timestamp Governed by policy, DevX tests/unit/commands/test_lock_export_command.py::test_export_without_generated_at_uses_fixed_epoch unit
4 Repeat an LSP install without rewriting the timestamp-free lockfile Multi-harness support, DevX tests/integration/test_install_lsp_lockfile_determinism.py::test_repeated_install_with_unchanged_lsp_keeps_lockfile_bytes integration
5 Repeat an Agent Plugin export from a timestamp-free lockfile and get identical archive bytes Secure by default, DevX tests/unit/test_agent_plugin_exporter.py::test_agent_bundle_archives_are_reproducible integration

How to test

  • Create a project lockfile and confirm generated_at is absent.
  • Add generated_at to an existing lockfile, make a substantive dependency change, and confirm the field remains present with a refreshed value.
  • Remove generated_at from that lockfile, write it again, and confirm the field is not reintroduced.
  • Run apm lock export without SOURCE_DATE_EPOCH and confirm the SBOM timestamp is 1970-01-01T00:00:00+00:00.
  • Repeat an unchanged MCP or LSP install and confirm lockfile bytes do not change.

Closes #2572

@lachieh

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes the volatile generated_at timestamp from newly created apm.lock.yaml files to reduce merge conflicts, while preserving legacy behavior for repositories that already have the field. It also ensures apm lock export remains deterministic by falling back to the Unix epoch when no timestamp source is available.

Changes:

  • Make LockFile.generated_at optional and omit it from YAML when absent, with legacy-preserve/refresh behavior during writes.
  • Remove MCP’s unconditional timestamp assignment so MCP-created lockfiles remain timestamp-free by default.
  • Update CLI/help text, docs, changelog, and tests to reflect the new/legacy behaviors and the SBOM timestamp fallback order.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/apm_cli/deps/lockfile.py Makes generated_at optional, omits it in YAML when absent, and refreshes/preserves it only for legacy lockfiles.
src/apm_cli/integration/mcp_integrator.py Stops assigning generated_at during MCP lockfile persistence to keep new lockfiles timestamp-free.
src/apm_cli/commands/lock.py Updates lock export help text to include the Unix epoch as the final deterministic fallback.
tests/test_lockfile.py Adds coverage to ensure generated_at is omitted from new YAML and refreshed for legacy lockfiles on substantive writes.
tests/unit/commands/test_lock_export_command.py Adds a test asserting SBOM export uses the Unix epoch when lockfile has no generated_at.
tests/unit/install/test_mcp_lockfile_determinism.py Updates determinism expectations for timestamp-free lockfiles and adds a legacy refresh test.
tests/unit/integration/test_mcp_integrator.py Verifies a missing lockfile created via MCP does not include generated_at.
tests/integration/test_install_mcp_lockfile_determinism.py Aligns MCP lifecycle assertions with timestamp-free lockfiles.
tests/integration/test_install_lsp_lockfile_determinism.py Aligns LSP lifecycle assertions with timestamp-free lockfiles.
tests/integration/test_cache_lockfile_parity.py Updates parity docs/assertion intent to treat generated_at as optional legacy metadata.
tests/integration/test_config_surface_lifecycle_contract.py Updates lifecycle contract wording to refer to optional legacy generated_at metadata.
tests/integration/test_oci_mcp_lifecycle_contract.py Updates idempotency wording to refer to optional legacy timestamp metadata.
docs/src/content/docs/reference/lockfile-spec.md Updates spec examples and marks generated_at as optional legacy metadata with opt-out semantics.
docs/src/content/docs/reference/cli/lock.md Documents SBOM timestamp fallback order including Unix epoch.
docs/src/content/docs/reference/cli/pack.md Removes timestamp from generated lockfile examples.
docs/src/content/docs/concepts/package-anatomy.md Updates the canonical example and marks generated_at as deprecated/optional legacy metadata.
packages/apm-guide/.apm/skills/apm-usage/commands.md Updates bundled command reference to match the new SBOM timestamp fallback order.
CHANGELOG.md Adds an Unreleased “Fixed” entry describing the timestamp omission and legacy behavior.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tests/unit/commands/test_lock_export_command.py
Comment thread CHANGELOG.md Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (1)

tests/unit/commands/test_lock_export_command.py:143

  • This test is asserting the final fallback behavior when generated_at is missing, but it currently relies on SOURCE_DATE_EPOCH being unset in the process environment. If SOURCE_DATE_EPOCH is set (common in reproducible-build contexts), the command will prefer it and this assertion will fail.
def test_export_without_generated_at_uses_fixed_epoch(runner, tmp_path):
    with runner.isolated_filesystem(temp_dir=tmp_path):
        _seed(Path.cwd())
        lock_path = Path("apm.lock.yaml")

@sergio-sisternes-epam

Copy link
Copy Markdown
Collaborator

APM Review Panel: ship_with_followups

Eliminates timestamp-only merge conflicts from new lockfiles while preserving backward compat for existing projects.

cc Lachlan Heywood (@lachieh) Daniel Meppiel (@danielmeppiel) Sergio Sisternes (@sergio-sisternes-epam) -- a fresh advisory pass is ready for your review.

All nine panelists converged: this is a clean, backward-compatible deprecation of a volatile field that caused real contributor friction. No security regressions, no auth surface touched, no CLI output changes, and the performance cost (one extra YAML parse per write, ~2-5 ms, once per invocation) is unmeasurable. The only substantive gap is a missing unit test for the no-op preservation path -- the test-coverage-expert flagged it with a governed-by-policy principle tag, and the python-architect confirmed the mutation contract is exercised only on the refresh path, not the preservation path. That gap is real but non-blocking: a silent drift would cause unnecessary lockfile churn on legacy files, which is the exact problem this PR solves for new files.

Strategically, this change reinforces APM's git-native, team-friendly positioning. Independent branches no longer collide on a timestamp line -- a friction point that scales with team size and is invisible to solo developers. The deprecation path (field preserved until manually removed) is the right call: it avoids a breaking change while giving teams a one-line opt-out.

The doc-writer's two recommended findings (deprecation label consistency and explicit migration mechanism) are worth folding before merge -- they are one-line edits that prevent a support question. The devx-ux-expert's CLI hint suggestion is interesting but risks log noise for a field that will naturally disappear as teams adopt; defer to a follow-up issue.

Aligned with: Portable by manifest (lockfile stays fully portable; removing the volatile field improves cross-environment reproducibility). Pragmatic as npm (zero-friction default for new projects; existing projects preserve behavior until explicit opt-out). OSS community-driven (directly addresses issue #2572, a contributor-filed friction report). Governed by policy (SBOM export gains a deterministic epoch fallback). Secure by default (no regression; reproducibility improves).

Growth signal. Story angle: 'Zero-conflict lockfiles -- independent branches no longer collide on a timestamp line.' Reinforces git-native, team-friendly positioning. Worth a release-note beat and a short social post targeting teams that have experienced lockfile churn in monorepos or multi-contributor OSS projects.

Panel summary

Persona B R N Takeaway
Python Architect 0 1 1 Clean backward-compatible deprecation of a volatile field. The write()-mutates-self pattern is the only architectural concern worth flagging.
CLI Logging Expert 0 0 1 No CLI output regressions. One observability gap: legacy generated_at preservation/refresh in write() has no verbose-mode breadcrumb.
DevX UX Expert 0 1 1 Clean UX win for new users (no surprise timestamp diff noise). Migration path for existing users documented but relies on reading the lockfile-spec page -- no CLI hint surfaces the action.
Supply Chain Security Expert 0 0 1 No supply-chain security regressions. Removing volatile generated_at from new lockfiles improves reproducibility. TOCTOU window in write() is pre-existing and bounded. SBOM epoch fallback does not weaken integrity or provenance.
OSS Growth Hacker 0 0 0 This PR removes a top contributor-friction source (timestamp merge conflicts) -- a story-shaped change worth a release beat. No conversion-surface regressions detected.
Doc Writer 0 2 2 Documentation changes are accurate and well-scoped. Three gaps: deprecation label formatting inconsistency between pages, migration mechanism not specified (manual edit), missing one-way guarantee on anatomy page.
Test Coverage Expert 0 1 0 Critical lockfile surfaces are well-covered. One recommended gap: legacy no-op preservation path at unit tier lacks a dedicated test.
Performance Expert 0 0 1 The new type(self).read(path) inside write() adds an unconditional disk read+YAML parse (~2-5 ms) on every lockfile write. Double-read pattern vs callers, but write() is called at most once per CLI invocation -- not measurable against install wall-time.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 5 follow-ups

  1. [Test Coverage Expert] Add test_write_preserves_existing_generated_at_on_noop unit test -- The no-op preservation contract (legacy lockfile timestamp survives unchanged when deps are identical) has no automated guardrail. Missing test on a governed-by-policy surface; drift here silently reintroduces the churn this PR eliminates.
  2. [Doc Writer] Align deprecation label in lockfile-spec.md to match package-anatomy.md format -- The normative spec page should carry the same (Deprecated) signal as the anatomy page. One-line edit, prevents reader confusion.
  3. [Doc Writer] Make migration instruction explicit: 'Delete the generated_at line; APM will not add it back' -- Current wording says 'remove the field' without specifying the mechanism is a manual edit. Users will search for a CLI command that does not exist.
  4. [DevX UX Expert] Consider a one-time CLI hint when legacy generated_at is refreshed -- Surfaces the opt-out to users who still experience timestamp diffs without requiring them to find the lockfile-spec doc. Risk: log noise for a naturally-disappearing field -- defer to issue.
  5. [Python Architect] Document write() mutation contract in a docstring -- write() mutates self.generated_at as a side effect. A one-line docstring makes the command-query separation violation intentional and discoverable.

Architecture

classDiagram
    direction LR
    class Lockfile {
        <<Dataclass>>
        +lockfile_version: str
        +generated_at: str | None
        +packages: list
        +read(path) Lockfile$
        +write(path) None
        +to_yaml() str
        +is_semantically_equivalent(other) bool
    }
    class McpIntegrator {
        <<BaseIntegrator>>
        +integrate(targets) None
    }
    class LockCommand {
        +export_lock() None
    }
    class atomic_write_text {
        <<Utility>>
    }
    McpIntegrator ..> Lockfile : builds and writes
    LockCommand ..> Lockfile : reads for export
    Lockfile ..> atomic_write_text : delegates I/O
    note for Lockfile "write() owns legacy-preservation decision:\nread existing -> compare -> mutate -> serialize"
    class Lockfile:::touched
    class McpIntegrator:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A["apm install / apm lock"] --> B["McpIntegrator.integrate()"]
    B --> C["Lockfile() constructor\ngenerated_at=None by default"]
    C --> D["Lockfile.write(path)"]
    D --> E{"path.exists()?"}
    E -->|No| F["atomic_write_text\nno generated_at field"]
    E -->|Yes| G["Lockfile.read(path)\nexisting lockfile"]
    G --> H{"existing.generated_at?"}
    H -->|None| F
    H -->|present| I{"semantically equivalent?"}
    I -->|Yes, no-op| J["preserve existing timestamp"]
    I -->|No, changed| K["refresh to now()"]
    J --> F
    K --> F
    F --> L["Done"]
Loading
sequenceDiagram
    participant User
    participant CLI as apm install
    participant MI as McpIntegrator
    participant LF as Lockfile
    participant FS as Filesystem
    User->>CLI: apm install
    CLI->>MI: integrate(targets)
    MI->>LF: Lockfile(...) [generated_at=None]
    MI->>LF: write(path)
    LF->>FS: read existing lockfile
    alt existing has generated_at
        alt semantically equivalent
            LF->>LF: self.generated_at = existing.generated_at
        else changed
            LF->>LF: self.generated_at = now()
        end
    end
    LF->>FS: atomic_write_text(yaml)
    FS-->>User: apm.lock.yaml updated
Loading

Recommendation

Ship now. The two doc-writer one-line fixes (deprecation label + explicit migration wording) are worth folding before merge if convenient but are not blocking. The missing no-op preservation test is the highest-signal follow-up -- file as a fast-follow issue. No panelist raised a blocking concern; the change is backward-compatible, well-scoped, and directly addresses a real contributor-friction report.


Full per-persona findings

Python Architect

  • [recommended] write() mutates self.generated_at as a side effect, breaking command-query separation at src/apm_cli/deps/lockfile.py
    The write() method both persists state AND mutates self.generated_at in place. This means calling write() changes the object identity silently. A purer design would compute the final generated_at in a private method. However, since LockFile is a mutable dataclass used as a short-lived builder, this is tolerable. Docstring documenting the mutation contract is the right fix.
    Suggested: Add a docstring to write() explicitly stating it may mutate self.generated_at for legacy preservation.
    Proof (passed): tests/test_lockfile.py::test_write_refreshes_existing_generated_at -- proves: The mutation path is exercised and asserted

  • [nit] generated_at type annotation uses str | None (3.10+ union syntax) -- fine for the supported Python range
    The codebase already uses this syntax elsewhere and pyproject.toml targets 3.10+.

CLI Logging Expert

  • [nit] No debug log breadcrumb in write() for legacy timestamp decisions at src/apm_cli/deps/lockfile.py
    The write() method silently preserves or refreshes generated_at for legacy lockfiles but emits no logger.debug() breadcrumb. A logger.debug('Preserving legacy generated_at') / logger.debug('Refreshing legacy generated_at') would help --verbose users and CI debug lockfile churn.

DevX UX Expert

  • [recommended] No CLI guidance surfaces when legacy generated_at is preserved during a substantive write at src/apm_cli/deps/lockfile.py:910
    When write() detects an existing generated_at and refreshes it, the user never learns they can remove the field to stop timestamp churn. A one-time _log.info hint like 'hint: remove generated_at from apm.lock.yaml to eliminate timestamp diffs' would surface the opt-out without requiring users to find the lockfile-spec doc.
    Suggested: Add a single hint log on first legacy refresh: _log.info('[i] Tip: remove the generated_at line from apm.lock.yaml to eliminate timestamp-only diffs')

  • [nit] Help text 'Unix epoch' may be unclear without the literal value at src/apm_cli/commands/lock.py:280
    'then the Unix epoch' may confuse users unfamiliar with the term. Consider 'then 1970-01-01T00:00:00+00:00 (deterministic zero)' to make the fallback value immediately obvious.

Supply Chain Security Expert

  • [nit] TOCTOU: write() reads existing lockfile then writes without holding a lock at src/apm_cli/deps/lockfile.py
    The write() method reads the on-disk lockfile then calls atomic_write_text. If another process writes between those two operations, the preserved timestamp may be stale. Pre-existing behavior, low-risk for a single-user CLI, but a comment documenting the assumption would help.

OSS Growth Hacker

No findings.

Auth Expert -- inactive

No auth-surface files touched; PR only alters lockfile timestamp serialization with no credential, token, or host-classification impact.

Doc Writer

Test Coverage Expert

  • [recommended] Legacy lockfile no-op write (semantically equivalent content) lacks a dedicated test proving generated_at is preserved unchanged at tests/test_lockfile.py
    The PR changes write() to refresh generated_at only on substantive changes. The preservation contract -- no-op write preserves the original timestamp -- has no dedicated test. test_write_refreshes_existing_generated_at proves the refresh path but not the preservation path. A silent drift here would cause unnecessary lockfile churn on legacy files.
    Suggested: Add test_write_preserves_existing_generated_at_on_noop: seed legacy lockfile with generated_at, call write() without changing deps, assert generated_at in output equals original value.
    Proof (missing at unit): tests/test_lockfile.py::test_write_preserves_existing_generated_at_on_noop -- proves: No-op write on legacy lockfile does not mutate generated_at, preventing unnecessary lockfile churn [devx,governed-by-policy]

Performance Expert

  • [nit] Double-read: write() re-reads the lockfile that callers already parsed at src/apm_cli/deps/lockfile.py:910
    Every call site reaching write() has already called LockFile.read(path) upstream (MCP integrator, install pipeline). The existing = type(self).read(path) here re-reads and re-parses the file. Cost is ~2-5 ms, write() is called at most once per CLI run -- not measurable. But accepting an optional existing: LockFile | None = None parameter would eliminate the redundancy cleanly.
    Suggested: Add existing: LockFile | None = None parameter to write(), read from disk only when existing is None and path.exists().

This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.

@sergio-sisternes-epam Sergio Sisternes (sergio-sisternes-epam) removed the panel-review Trigger the apm-review-panel gh-aw workflow label Aug 18, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/apm_cli/deps/lockfile.py:916

  • The on-disk opt-in is not authoritative in two cases: a new or timestamp-free destination still emits any non-null self.generated_at, and a semantic no-op against a legacy file keeps a differing in-memory timestamp instead of the persisted one. This contradicts the documented remove-once opt-out and stable no-op behavior. Normalize the field entirely from the current on-disk state before serialization.
        existing = type(self).read(path) if path.exists() else None
        if existing is not None and existing.generated_at is not None:
            if self.is_semantically_equivalent(existing):
                if self.generated_at is None:
                    self.generated_at = existing.generated_at
            else:
                self.generated_at = datetime.now(timezone.utc).isoformat()

src/apm_cli/deps/lockfile.py:916

  • This block becomes the canonical owner of lockfile timestamp policy, and the PR removes the parallel MCP assignment, but no static architecture guard prevents a caller from reintroducing direct generated_at writes. Add a boundary check in scripts/lint-architecture-boundaries.sh plus its matching architecture test so every persistence path must continue routing through LockFile.write().
        existing = type(self).read(path) if path.exists() else None
        if existing is not None and existing.generated_at is not None:
            if self.is_semantically_equivalent(existing):
                if self.generated_at is None:
                    self.generated_at = existing.generated_at
            else:
                self.generated_at = datetime.now(timezone.utc).isoformat()

@lachieh
Lachlan Heywood (lachieh) marked this pull request as draft August 19, 2026 02:28
@lachieh
Lachlan Heywood (lachieh) marked this pull request as ready for review August 19, 2026 02:29
@lachieh

Copy link
Copy Markdown
Author

Thanks for triggering the review, Sergio Sisternes (@sergio-sisternes-epam). I've implemented the follow-ups now instead of waiting. Let me know if you need anything else.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 22, 2026
@lachieh
Lachlan Heywood (lachieh) force-pushed the fix/2572-conditional-generated-at branch from c177d44 to 1249581 Compare August 22, 2026 20:20
@lachieh

Copy link
Copy Markdown
Author

Daniel Meppiel (@danielmeppiel), could you try merging this again? I’ve pushed the CI fix, and both coverage test shards now pass locally.

@danielmeppiel

Copy link
Copy Markdown
Collaborator

APM Review Panel: needs_rework

Timestamp-free lockfiles reduce churn, but deterministic Agent Plugin archives must be restored before shipping.

cc Lachlan Heywood (@lachieh) Sergio Sisternes (@sergio-sisternes-epam) -- a fresh advisory pass is ready for your review.

The panel supports omitting generated_at from new lockfiles and preserving legacy refresh behavior. However, three specialists identified a downstream correctness regression: a timestamp-free lockfile passes None into Agent Plugin packing, which falls back to wall-clock time and changes archive bytes and digests between identical exports.

The test-coverage expert's clean return does not outweigh the specific missing integration guard identified at tests/unit/test_agent_plugin_exporter.py: assert first.bundle_path.read_bytes() == second.bundle_path.read_bytes(). Because this missing test covers secure-by-default and portability-by-manifest promises, it carries high weight. Restore a deterministic fallback, add the timestamp-free archive variant, and enforce centralized timestamp ownership. Changelog and help wording should then distinguish new-lockfile omission from legacy preservation.

Aligned with: Portable by manifest: identical manifests and lockfiles should produce byte-identical portable archives; secure by default: stable archive bytes preserve meaningful digest-based artifact verification; governed by policy: a static ownership guard keeps timestamp policy centralized and auditable; pragmatic as npm: omitting generated timestamps reduces routine lockfile churn without requiring user configuration.

Panel summary

Persona B R N Takeaway
Python Architect 1 1 0 Restore Agent Plugin archive reproducibility and guard canonical timestamp ownership.
CLI Logging Expert 0 0 1 CLI output is accurate; only the help text lacks the legacy qualifier.
DevX UX Expert 1 0 0 Preserve a stable pack timestamp for new lockfiles.
Supply Chain Security Expert 1 0 0 Identical Agent Plugin exports must retain identical archive digests.
OSS Growth Hacker 0 1 1 Clarify when existing repositories receive the conflict-reduction benefit.
Doc Writer 0 1 0 Separate the new-lockfile benefit from legacy preservation.
Test Coverage Expert 0 0 0 Existing lockfile and SBOM promises have regression coverage.
Performance Expert 0 1 0 The write path reparses existing YAML unnecessarily.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 3 follow-ups

  1. [Supply Chain Security Expert] (blocking-severity) Use SOURCE_DATE_EPOCH, then the Unix epoch, when packing from timestamp-free lockfiles and add the archive reproducibility test -- wall-clock metadata currently makes identical exports produce different bytes and digests.
  2. [Python Architect] Add a static architecture check preventing generated_at assignments outside the canonical lockfile timestamp owner -- automated enforcement prevents future call sites from bypassing the centralized policy.
  3. [Doc Writer] Clarify the changelog and --timestamp help wording for new versus legacy lockfiles -- conflict reduction comes from omission, while existing repositories retain the legacy field until removal.

Architecture

classDiagram
    class LockFile {
      +generated_at str?
      +to_yaml() str
      +write(path) None
    }
    class MCPIntegrator
    class AgentPluginExporter
    class LockfileEnrichment
    class LockExportModule
    MCPIntegrator ..> LockFile : delegates persistence
    AgentPluginExporter ..> LockFile : reads generated_at
    AgentPluginExporter ..> LockfileEnrichment : passes packed_at
    LockExportModule ..> LockFile : reads
Loading
flowchart TD
    A["Install or lock"] --> W["LockFile.write"]
    W --> G{"Existing generated_at?"}
    G -- no --> O["Omit generated_at"]
    G -- yes --> S{"Semantic content changed?"}
    S -- no --> P["Preserve legacy timestamp"]
    S -- yes --> R["Refresh legacy timestamp"]
    O --> D["Deterministic lockfile"]
    P --> D
    R --> D
    AP["Agent Plugin pack"] --> E["generated_at passed as packed_at"]
    E --> N{"packed_at is None?"}
    N -- yes --> C["Current wall clock"]
    C --> X["Archive bytes differ"]
Loading

Recommendation

Revise the PR to restore deterministic Agent Plugin archive timestamps and add the timestamp-free reproducibility regression test. Include the timestamp-ownership guard and in-scope migration wording before asking maintainers to ship.


Full per-persona findings

Python Architect

  • [blocking] Timestamp-free lockfiles make Agent Plugin archives non-reproducible at src/apm_cli/deps/lockfile.py:709.
    agent_plugin_exporter forwards None as packed_at, and lockfile enrichment substitutes the wall clock.
    Suggested: Use SOURCE_DATE_EPOCH, then the Unix epoch, and extend archive reproducibility coverage.
  • [recommended] Guard centralized lockfile timestamp ownership statically at src/apm_cli/deps/lockfile.py:914.
    The change centralizes timestamp policy and removes the MCP assignment, so canonical authority discipline requires a static boundary check and matching architecture test.

CLI Logging Expert

  • [nit] Label generated_at as legacy in --timestamp help at src/apm_cli/commands/lock.py:280.
    Documentation uses the qualifier, and CLI help should match.

DevX UX Expert

  • [blocking] Timestamp-free lockfiles break reproducible Agent Plugin archives at src/apm_cli/deps/lockfile.py:709.
    Repeated packs from identical inputs receive different wall-clock metadata.
    Proof (missing at): tests/unit/test_agent_plugin_exporter.py::test_agent_bundle_archives_are_reproducible.

Supply Chain Security Expert

  • [blocking] Timestamp-free lockfiles make identical Agent Plugin exports produce different archive bytes at src/apm_cli/bundle/agent_plugin_exporter.py:499.
    Changing archive digests weakens independent artifact verification.
    Proof (missing at): tests/unit/test_agent_plugin_exporter.py::test_agent_bundle_archives_are_reproducible timestamp-free variant.

OSS Growth Hacker

  • [recommended] Separate the new-lockfile benefit from legacy compatibility in release messaging at CHANGELOG.md:26.
    Existing users need a clear one-time removal migration to receive the benefit.
  • [nit] Call generated_at legacy in CLI help too at src/apm_cli/commands/lock.py:280.

Auth Expert -- inactive

The touched lockfile, MCP integration, and lock command files do not affect authentication.

Doc Writer

  • [recommended] Separate the new-lockfile benefit from legacy timestamp preservation at CHANGELOG.md:27.
    The current grammar can imply that refreshing legacy timestamps prevents conflicts.

Test Coverage Expert

No findings.

Performance Expert

  • [recommended] Avoid reparsing the existing lockfile on every write at src/apm_cli/deps/lockfile.py:928.
    Timestamp-free files add one full YAML parse and legacy files can parse twice.
    Suggested: Parse existing text once and consider accepting an already-loaded lockfile.

This panel is advisory. It does not block merge. Re-apply the panel-review label after addressing feedback to re-run.

@danielmeppiel

Copy link
Copy Markdown
Collaborator

APM Spec Guardian: editorial-only

Scope: editorial-only; diff = +12/-10 lines across 1 file(s). Shocked-meter avg: n/a.

No substantive spec change detected. Wave 3 panel fan-out skipped; only the linter ran on the modified artifact.

Linter notes (1 check failed)

  • [11] This PR also modifies Python files; the general APM review panel already reviewed those changes.

This panel is advisory. It does not block merge. Re-apply the spec-review label to re-run.

@danielmeppiel

Copy link
Copy Markdown
Collaborator

APM Review Panel: ship_now

PR #2616 makes lockfile generation byte-reproducible while preserving clear ownership, accurate documentation, and efficient writes.

cc Lachlan Heywood (@lachieh) Sergio Sisternes (@sergio-sisternes-epam) -- a fresh advisory pass is ready for your review.

The panel converges without unresolved concerns. Every recommendation was folded into e3813d975641d00188ecc2984a91af94952c5355: static ownership enforcement, stale example cleanup, precise epoch wording, Agent Plugin reproducibility evidence, and reuse of the destination read during changed writes.

The validation signal is strong: 116 focused tests and 164 conformance tests passed, with two skips, while lint, formatting, architecture, auth, and static-boundary checks also passed. All GitHub workflows are green on the final head. Integrity and deterministic behavior remain intact, and the public explanation now matches the implementation.

Aligned with: Portable by manifest -- equivalent dependency state produces stable lockfile bytes. Secure by default -- integrity stays intact and the ownership guard prevents timestamp-policy drift. OSS community driven -- examples and release text now state the contract precisely. Pragmatic as npm -- deterministic output requires no extra configuration and changed writes avoid a redundant YAML parse.

Growth signal. Byte-reproducible lockfiles are a concrete trust story for teams adopting APM: stable diffs, clearer provenance, and fewer environment-dependent surprises.

Panel summary

Persona B R N Takeaway
Python Architect 0 1 0 Runtime ownership is coherent; the recommended fallback guard is folded.
CLI Logging Expert 0 0 0 CLI help clearly states the deterministic timestamp fallback.
DevX UX Expert 0 1 0 The implementation and examples now converge on the timestamp-free default.
Supply Chain Security Expert 0 0 0 Lockfile integrity and deterministic output remain intact.
OSS Growth Hacker 0 1 0 The deterministic-lockfile story and release wording are aligned.
Doc Writer 0 2 0 Epoch wording and OpenAPM examples now match the implementation.
Test Coverage Expert 0 1 0 Archive reproducibility is tested and mapped in Scenario Evidence.
Performance Expert 0 1 0 Changed installs reuse the fresh destination parse.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Architecture

classDiagram
    direction LR
    class LockfileModule {
      <<CanonicalAuthority>>
      +resolve_reproducible_timestamp(explicit, generated_at) str
    }
    class LockFile {
      <<DataclassAggregate>>
      +generated_at str?
      +write(path, existing_lockfile) None
      +to_yaml() str
      +is_semantically_equivalent(other) bool
    }
    class MCPIntegrator
    class LockCommandModule {
      <<CLIAdapter>>
    }
    class AgentPluginExporterModule {
      <<BundleAdapter>>
    }
    LockfileModule *-- LockFile : defines
    MCPIntegrator ..> LockFile : delegates persistence
    LockCommandModule ..> LockfileModule : resolves timestamp
    AgentPluginExporterModule ..> LockfileModule : resolves packed_at
    class LockfileModule:::touched
    class LockFile:::touched
    class MCPIntegrator:::touched
    class LockCommandModule:::touched
    class AgentPluginExporterModule:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A["Install builds lockfile"] --> B["Read destination once"]
    B --> C["LockFile.write with existing state"]
    C --> D{"Destination has generated_at?"}
    D -- No --> E["Omit timestamp"]
    D -- Yes --> F{"Semantic content changed?"}
    F -- No --> G["Preserve legacy timestamp"]
    F -- Yes --> H["Refresh legacy timestamp"]
    I["SBOM or Agent Plugin export"] --> J["resolve_reproducible_timestamp"]
    J --> K["Explicit timestamp"]
    J --> L["SOURCE_DATE_EPOCH"]
    J --> M["Legacy generated_at"]
    J --> N["Fixed epoch"]
Loading

Recommendation

Ship this change. All panel recommendations are incorporated, exact-head validation passes, all GitHub workflows are green, and no additional follow-up remains.


Full per-persona findings

Python Architect

  • [recommended] Extend the static boundary to defend reproducible fallback ownership at scripts/lint-architecture-boundaries.sh:247.
    The owner table covers both timestamp writes and fallback policy. Folded: the AST guard and mutation test now reject direct SOURCE_DATE_EPOCH or fixed-epoch policy outside deps/lockfile.py.

CLI Logging Expert

No findings.

DevX UX Expert

  • [recommended] Align remaining spec examples with the timestamp-free default at docs/src/content/docs/specs/openapm-v0.1.md:822.
    Folded: all three unqualified producer examples now omit generated_at.

Supply Chain Security Expert

No findings.

OSS Growth Hacker

  • [recommended] Align the release-note timestamp claim with shipped behavior at CHANGELOG.md:31.
    Folded: the entry now describes byte reproducibility through SOURCE_DATE_EPOCH or a fixed epoch without conflating metadata fields.

Auth Expert -- inactive

Touched files cover lockfile timestamp handling, bundle export, and related docs, specs, and tests; no authentication behavior changed.

Doc Writer

  • [recommended] Correct the Agent Plugin fallback epoch description at CHANGELOG.md:31.
    Folded with precise byte-reproducibility wording.
  • [recommended] Align the OpenAPM lockfile examples with the new default at docs/src/content/docs/specs/openapm-v0.1.md:1147.
    Folded by removing generated_at from the three default examples.

Test Coverage Expert

  • [recommended] Map Agent Plugin archive reproducibility in Scenario Evidence at src/apm_cli/bundle/agent_plugin_exporter.py:504.
    Folded into the PR body with tests/unit/test_agent_plugin_exporter.py::test_agent_bundle_archives_are_reproducible; its four archive variants pass.

Performance Expert

  • [recommended] Avoid reparsing the existing lockfile during changed writes at src/apm_cli/deps/lockfile.py:957.
    Folded: the install builder passes its fresh destination read into LockFile.save, and a regression test proves no second YAML load occurs.

This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.

@lachieh
Lachlan Heywood (lachieh) force-pushed the fix/2572-conditional-generated-at branch from e3813d9 to 668a03f Compare August 24, 2026 14:05
@lachieh
Lachlan Heywood (lachieh) force-pushed the fix/2572-conditional-generated-at branch 2 times, most recently from e3813d9 to 4e22dbc Compare August 24, 2026 14:36
@lachieh

Copy link
Copy Markdown
Author

Daniel Meppiel (@danielmeppiel) originally clobbered some of your commits, so restored and rebased on main to correct merge conflicts. Should be good now!

@lachieh
Lachlan Heywood (lachieh) force-pushed the fix/2572-conditional-generated-at branch 2 times, most recently from d4bd46f to 991efc4 Compare August 25, 2026 16:37
@danielmeppiel
Daniel Meppiel (danielmeppiel) force-pushed the fix/2572-conditional-generated-at branch from 991efc4 to e067b3e Compare August 25, 2026 19:59
@lachieh
Lachlan Heywood (lachieh) force-pushed the fix/2572-conditional-generated-at branch from e067b3e to 5d31068 Compare August 30, 2026 21:11
@danielmeppiel

Copy link
Copy Markdown
Collaborator

APM Review Panel: ship_with_followups

Deterministic lockfiles remove volatile diffs while preserving explicit legacy timestamp behavior and reproducible SBOM exports.

cc Lachlan Heywood (@lachieh) Sergio Sisternes (@sergio-sisternes-epam) -- a fresh advisory pass is ready for your review.

The panel converges that this is a well-scoped reliability improvement: new lockfiles become reproducible, legacy files retain predictable compatibility semantics, and deleting generated_at provides a clear opt-in to timestamp-free output. This directly advances P6 - Reliability over magic - because every timestamp transition is explicit, documented, and covered across lockfile and SBOM behavior.

No security, CLI-output, or DevX regression was identified, and the test-coverage review found the core creation, legacy, SBOM fallback, and MCP/LSP determinism scenarios represented. The remaining architecture, malformed-file repair, and measured reparse-cost findings are valuable hardening opportunities for this scoped correction.

Aligned with: Portable by manifest: stable lockfiles and deterministic SBOM timestamps make the same manifest-derived state reproducible across machines and automation. Pragmatic as npm: the change removes noisy diffs without adding flags or workflow burden, while keeping legacy behavior explicit and compatible under P6.

Growth signal. Reproducible lockfiles are a concrete package-manager trust story: fewer meaningless diffs, cleaner collaboration, and deterministic automation.

Panel summary

Persona B R N Takeaway
Python Architect 0 2 0 Timestamp policy is correctly centralized, with two recommended guardrail and repairability fixes.
CLI Logging Expert 0 0 0 CLI help and verbose diagnostics remain accurate, concise, and appropriately scoped.
DevX UX Expert 0 0 0 The change improves lockfile determinism and merge ergonomics while documenting legacy behavior clearly.
Supply Chain Security Expert 0 0 0 No supply-chain security concerns; integrity, provenance, and fail-closed behavior remain intact.
OSS Growth Hacker 0 0 0 Clear user benefit: new lockfiles avoid timestamp-only conflicts while preserving deterministic exports.
Doc Writer 0 0 1 Documentation is accurate and discoverable; only the CHANGELOG wording needs minor clarification.
Test Coverage Expert 0 0 0 Timestamp-free creation, legacy handling, SBOM fallback, and MCP/LSP determinism have regression traps.
Performance Expert 0 1 0 An avoidable O(B) lockfile reparse adds measurable cost per existing-file write.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 3 follow-ups

  1. [Python Architect] Make malformed legacy timestamp-bearing lockfiles repairable during substantive replacement. -- Optional generated_at metadata should not determine whether APM can atomically repair an otherwise replaceable destination.
  2. [Performance Expert] Pass preloaded lockfile snapshots through hot writers, beginning with MCPIntegrator, and add parse-count or scaling guards. -- The measured redundant parse is avoidable where callers already hold the parsed value.
  3. [Python Architect] Extend the architecture boundary guard to detect LockFile(generated_at=...) constructor bypasses. -- Protecting the canonical timestamp-policy owner prevents future call sites from silently fragmenting behavior.

Architecture

classDiagram
    direction LR
    class LockfileModule {
      <<PolicyModule>>
      +resolve_reproducible_timestamp(explicit, legacy) str
    }
    class LockFile {
      <<Facade>>
      +generated_at optional str
      +write(path, existing_lockfile) None
      +save(path, existing_lockfile) None
      +is_semantically_equivalent(other) bool
    }
    class LockfileBuilder {
      <<Orchestrator>>
      +_write_if_changed(lockfile, path, type) None
    }
    class MCPIntegrator {
      +update_lockfile(names, path) None
    }
    class LockCommandModule {
      <<Module>>
      +lock_export(fmt, output, global, timestamp) None
    }
    LockfileModule o-- LockFile : defines
    LockfileBuilder ..> LockFile : saves preloaded destination
    MCPIntegrator ..> LockFile : persists MCP state
    LockCommandModule ..> LockfileModule : resolves SBOM timestamp
Loading
flowchart TD
    I["apm install"] --> B["LockfileBuilder._write_if_changed"]
    I --> M["MCPIntegrator.update_lockfile"]
    B --> S["LockFile.save(existing_lockfile=existing)"]
    M --> S2["LockFile.save(path)"]
    S --> W["LockFile.write"]
    S2 --> W
    W --> G{"Existing destination carries generated_at?"}
    G -->|no| N["Omit generated_at"]
    G -->|yes, no-op| P["Preserve generated_at"]
    G -->|yes, changed| R["Refresh generated_at"]
    N --> A["Atomic write"]
    P --> A
    R --> A
    E["apm lock export"] --> H["resolve_reproducible_timestamp"]
    H --> D{"Explicit or SOURCE_DATE_EPOCH?"}
    D -->|yes| X["Use supplied timestamp"]
    D -->|no| L{"Legacy generated_at?"}
    L -->|yes| X
    L -->|no| U["Use Unix epoch"]
Loading

Recommendation

Ship the reproducibility fix on its current scope, then prioritize the malformed legacy-file repair path; track the constructor-boundary guard and redundant parsing optimization as focused hardening work.


Full per-persona findings

Python Architect

  • [recommended] Cover LockFile constructor timestamps in the ownership guard at scripts/lint-architecture-boundaries.sh:293
    The AST guard detects attribute assignments but not LockFile(generated_at=...), leaving a direct path for split authority to return.
    Suggested: Detect LockFile calls with a generated_at keyword outside deps/lockfile.py and add a matching negative fixture.
  • [recommended] Keep malformed legacy destinations repairable at src/apm_cli/deps/lockfile.py:975
    A malformed timestamp-free destination is repairable, while the same destination with deprecated generated_at raises before atomic replacement.
    Suggested: Treat an unconstructable legacy destination as a substantive replacement while retaining its timestamp policy, and add the timestamp-bearing counterpart test.

CLI Logging Expert

No findings.

DevX UX Expert

No findings.

Supply Chain Security Expert

No findings.

OSS Growth Hacker

No findings.

Auth Expert -- inactive

The diff does not touch authentication, token resolution, host classification, authorization headers, or AuthResolver call sites.

Doc Writer

  • [nit] Clarify when legacy generated_at is preserved versus refreshed at CHANGELOG.md:106
    Existing lockfiles preserve it on semantic no-ops and refresh it only on substantive writes.

Test Coverage Expert

No findings.

Performance Expert

  • [recommended] Thread preloaded lockfiles through every hot-path write at src/apm_cli/deps/lockfile.py:968
    LockFile.write() reparses an existing destination when existing_lockfile is omitted. MCPIntegrator already holds the parsed value but omits it when saving.
    Suggested: Pass pre-mutation snapshots through hot writers and add a parse-count regression guard.

This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.

Keep legacy timestamp metadata from making otherwise replaceable malformed lockfiles irreparable. Addresses panel lockfile repair follow-up.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid reparsing lockfile YAML when MCP persistence already loaded the exact destination. Addresses panel performance follow-up.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extend the canonical-owner guard to catch LockFile generated_at constructor writes outside the owner. Addresses panel architecture follow-up.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel

Copy link
Copy Markdown
Collaborator

Shepherd terminal advisory

All in-scope panel follow-ups were folded into this branch:

  • Malformed legacy timestamp-bearing lockfiles are now repairable (9c8b1e5a37).
  • MCP persistence reuses its preloaded destination snapshot (6792ba3120).
  • The timestamp-owner guard rejects LockFile(generated_at=...) bypasses (e9d6f82ce5).

Regression-trap mutation checks failed as expected with each guard removed, then passed after restoration. The affected unit set passed (161 tests), the architecture boundary lint passed, and the branch CI lint mirror completed successfully.

GitHub Actions did not create a microsoft/apm workflow run for exact head e9d6f82ce509d66aa53d465a9f1122e048f82d95; only license/cla ran and passed. GitHub reports CONFLICTING / DIRTY against current main, so observed-green CI cannot be claimed before the parent conflict-resolution phase rebases this PR.

@danielmeppiel

Copy link
Copy Markdown
Collaborator

Lachlan Heywood (@lachieh) thanks a lot for this work, took me a bit of time but here we are, merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] generated_at key in apm-lock.yml is constant source of merge conflicts.

4 participants