6.06  2026-10-29

  - Fix the installer bootstrap regression test by isolating TMPDIR and
    deriving expected Homebrew temporary paths from it. The install.sh runtime
    already honoured TMPDIR; no production installer behavior changed.

6.05  2026-10-28

  - Complete the d2 ps1 XS feasibility study against the installed 6.03
    baseline. Measured routes remained within noise at 0.98x to 1.03x, and
    no Perl routine had enough measured cost to justify an XS conversion.
    Preserve the existing implementation and record the evidence in the
    prompt performance investigation document.
  - Pin Plack's transitive HTTP::Tiny dependency to 0.096, the first stable
    release with cross-origin credential stripping and HTTPS downgrade
    redirect protections.
  - Isolate prompt performance tests from inherited workspace and tmux
    environment variables, and cover the overloaded-reference Perl fallback
    for HTML attribute escaping.
  - Align the main installation and release guide with the actual installer,
    Scorecard, security, and coverage gates.

6.04  2026-10-18

  - Freeze the final Perl reference implementation at 6.04 before continuing
    the C++ rewrite. This is a release-metadata change only; Perl runtime
    behavior is unchanged.

6.03  2026-10-18

  - Bump the final Perl distribution and every library module to 6.03 as the
    C++ rewrite moves into the dedicated CODE workspace. No Perl runtime
    behavior changed; the existing Perl implementation remains the reference
    for C++ feature-parity work.

6.02  2026-10-18

  - Finalize the measured Perl implementation snapshot before the C++ rewrite.
    Exclude the Archive/, OldFiles/, and CODE/ workspaces from the Perl
    distribution so nested repositories and operator data cannot leak into
    the release tarball. No Perl runtime behavior changed.

6.01  2026-10-18

  - Problem 48 performance-cycle wrap-up: retain only measured, behavior-
    preserving changes and document the workload limits of each result.
    The full Docker gate passes 285 test files / 25,326 tests with statement,
    branch, condition, and subroutine coverage at 100.0% across lib/.
    The saved-page route exceeds 50x over the v5.88 baseline for the measured
    fixtures, but the requested every-subroutine 50x target remains unproven.

6.00  2026-10-18

  - Problem 48 saved-page route normalization: return canonical flat ASCII
    ids directly rather than running trimming and prefix-removal expressions.
    Red-first Docker tests require this fast path and preserve the existing
    normalization for nested, prefixed, whitespace-bearing, Unicode,
    undefined, and reference inputs. The latest full gate passes 285 files / 25,326
    tests with all four lib/ coverage metrics at 100.0%.
    Three paired route comparisons retained the exact response hash; medians
    improved from 0.422330 to 0.347531 ms/request (1.21x). The all-routine
    50x objective remains open.

  - Problem 48 navigation rendering: concatenate ordered saved and skill nav
    fragments directly and wrap once, avoiding a temporary list array and a
    final join on the warmed route. Red-first Docker tests pin exact wrapper
    output. A 10-million-call comparison preserved output and measured 1.68x
    for the wrapper workload; route response bytes remain identical and no
    route-wide gain is claimed. The saved-nav cache-miss path also uses scalar
    accumulation instead of an intermediate array and join; a 20-fragment
    helper benchmark measured 2.09x, while the full route change was within
    noise and is not claimed as a route speedup.

  - Problem 48 web configuration snapshots: replace pretty-JSON construction
    of the private environment comparison key with versioned, length-prefixed
    framing that distinguishes missing, undefined, and defined selectors.
    Red-first Docker tests cover selector changes and delimiter-bearing values;
    nine alternating direct-method samples measured 1.34x faster. End-to-end
    seven paired route samples returned the exact same response hash, but their
    2.2% median difference was inconsistent across pairs; no route-level gain
    is claimed.

  - Problem 48 PathRegistry: cache the deepest runtime root only inside the
    explicit operation/request layer snapshot. A red-first Docker regression
    verifies one layer-inventory read across repeated scoped accesses and fresh
    results between unscoped calls. Nine alternating Docker microbench samples
    measured 1.72x for the accessor and 1.24x for the complete all_paths
    inventory with exact JSON parity. The matched saved-page route showed no
    measurable latency gain, so no route-level claim is made.

  - Problem 48: replace two repeated host-normalization substitutions with
    lowercase scheme prefix checks and one path-separator lookup. Characterized
    legacy case and URL behavior first; nine alternating Docker samples measured
    1.32x faster for the helper with exact parity. The saved-page route response
    was unchanged, and no route-level gain is claimed.

  - Problem 48 page chrome: reuse the worker-local prompt summary only after
    collector configuration and indicator freshness validation confirms the
    status is unchanged. Collector synchronization from the status route now
    invalidates the summary when configured indicator data changes, preventing
    a later page render from reusing stale text. Docker tests verify both
    invalidation paths along with fresh, changed, unchanged, and invalid cache
    states. With 50 indicators on a 100-skill/20-saved-nav fixture, the matched route measured
    1.117690 ms/request without reuse and 0.435739 ms/request with it (2.56x),
    with exact response bytes. The forced portable-watcher fallback preserved
    bytes but showed no measurable speedup.

  - Problem 48: rerun the complete Docker coverage gate after prompt-summary
    invalidation. All 285 files and 25,293 tests passed; statement, branch,
    condition, and subroutine coverage are each 100.0% across lib/ (51,645
    detail rows, no stale uncoverable annotations). Optional browser, Windows
    QEMU, CPANTS, POD, and audit-tool checks were skipped for unavailable
    external requirements. This does not prove the 50x all-subroutine target.

  - Problem 48 parser optimization: compile the legacy Markdown separator
    pattern once per module rather than once per page parse. A red-first CRLF
    and padded-separator regression passes, and nine alternating Docker runs
    measured 637.245 ms before versus 602.597 ms after for 100,000 two-section
    parses (1.06x) with exact parsed-output parity. The saved-page route kept
    identical response bytes; its measured latency change was within noise.

  - Problem 48 packaging regression: a named Devel::Cover database such as
    cover_db-p48-focus was not excluded by the underscore-only gather rule and
    leaked into the 6.00 tarball. Extend gather, prune, and manifest-skip rules
    to both hyphenated and underscored database names; add TDD coverage for
    exclusion matching and archive contents. The rebuilt archive contains no
    coverage database members; t/15-release-metadata.t passes 5,756 assertions
    and the blank-container cpanm install plus integration run passes.

  - Problem 48: make the saved-page route benchmark report native watcher
    availability and actual saved-nav snapshot hits, and add --no-native-watch
    to exercise the portable fallback on systems where FFI is installed.
    Benchmark tests compare output against the default run's response hash and
    assert native-cache counts only when the backend is available. The skill
    dispatcher unit test injects its deterministic watcher so it does not
    require optional FFI::Platypus.
  - Fix lazy watcher loading after route code changes directories: the runtime
    now resolves the watcher module from the caller module's absolute source
    path, while respecting an already loaded module. Regression test
    t/08-web-update-coverage.t previously returned HTTP 500 for skill pages;
    the focused 6.00 Docker suite passes 722 assertions after the fix.

5.99  2026-10-18

  - Problem 48 packaging verification exposed two tests that incorrectly
    depended on the development environment: aggregate watcher path filtering
    required the optional FFI backend, and the route benchmark compared output
    against a hard-coded repository-specific SHA-256. Inject a deterministic
    watcher in the unit test and compare optimization modes against the default
    run's response hash. The red-first blank Docker install failed on those
    checks; the updated focused Docker tests pass 425 assertions. The rebuilt
    tarball test then exposed additional native-cache count assumptions when
    optional FFI was unavailable; 6.00 makes those checks capability-aware.
    The complete 6.00 blank-environment install and integration run later
    passed after the native-cache assertions became capability-aware.

5.98  2026-10-18

  - Problem 48: render the top-chrome prompt summary with a single map/join
    pass instead of copying the indicator array and building a second list.
    A Docker regression protects ordering, Unicode aliases, and input
    immutability. Nine alternating 100,000-call samples over 50 indicators
    measured 865.926 ms before and 797.271 ms after (1.09x); this is a narrow
    helper gain and not a 50x route or all-subroutine result.

  - Problem 48: replace the top-chrome mode read through PageDocument::as_hash
    with a scalar mode accessor. A red-first Docker test fails if the renderer
    materializes the full page hash; focused page-document/web-app suites pass
    629 assertions. Nine alternating million-call samples measured 1,010.759
    ms through as_hash->{mode} and 213.278 ms through mode() (4.74x).

  - Problem 48: add a canonical flat-id validation fast path for ASCII page
    names, avoiding redundant normalization and path splitting while leaving
    prefixes, nested paths, Unicode, refs, and invalid components on the full
    validation path. Red-first Docker tests pass in the 216-assertion
    PageStore suite. Nine alternating million-call samples measured 879.049
    ms for the prior path and 430.213 ms for the fast path (2.04x).

  - Problem 48: rerun the full Docker coverage gate after flat-id validation.
    All 285 files and 25,238 tests pass; statement, branch, condition, and
    subroutine coverage are 100.0% across lib/ (51,588 detail rows, no stale
    uncoverable annotations). This does not prove the all-routine 50x target.

  - Problem 48: rerun the full Docker coverage gate after adding the scalar
    PageDocument mode accessor. All 285 files / 25,242 tests pass with 100.0%
    statement, branch, condition, and subroutine coverage across lib/ (51,597
    detail rows; no stale uncoverable annotations). Optional-tool checks remain
    separate and the universal 50x objective remains open.

  - Problem 48: rerun the full Docker coverage gate after the PageDocument id
    accessor and nav-renderer regression. All 285 files and 25,236 tests pass;
    statement, branch, condition, and subroutine coverage are 100.0% across
    lib/ (51,582 detail rows, no stale uncoverable annotations). Optional
    browser, Windows, CPANTS, POD, and audit-tool checks remain separate.

  - Problem 48: add a lightweight PageDocument id accessor and use it in the
    web renderer instead of constructing the full public page hash for an id
    lookup. Red-first Docker tests confirm the nav id path never calls as_hash;
    PageDocument and web-app suites pass 625 assertions. Nine alternating
    million-call samples measured 5.56x for id() over as_hash->{id}. A matched
    v5.88 route fixture retained exact HTTP/body parity and measured 158.53x
    overall; this cumulative result does not establish 50x for every routine.

  - Problem 48: after the fetch-metadata classifier fast paths and security
    regression assertions, rerun the full Docker coverage gate. All 285 test
    files and 25,233 tests pass; statement, branch, condition, and subroutine
    metrics are 100.0% across lib/ (51,573 detail rows, no stale uncoverable
    annotations). Optional gates requiring unavailable tools remain explicit.

  - Problem 48: add a no-fetch-metadata fast return to the CSRF fetch-site
    classifier. Direct comparison over nine alternating samples of one million
    headerless requests measured 370.968 ms before and 298.395 ms after (1.24x)
    with identical false results. The Docker cross-site regression suite passes
    34 assertions, including absent, undefined, and reference-valued headers.
    This narrow path gain does not establish the all-subroutine 50x objective.

  - Problem 48: return immediately for exact same-origin and none fetch-site
    labels, which are known non-foreign values. Scalar normalization remains
    unchanged for all other labels, including case- and whitespace-varied
    security inputs. The Docker security suite passes 35 assertions. Nine
    alternating million-call samples measured 830.125 ms before and 442.506 ms
    after (1.88x versus the preceding absent-header optimization).

  - Problem 48: remove the extra Perl helper dispatch from the source-only HTML
    body-escape fallback. A red-first Docker regression failed when the public
    fallback called the reference helper; after inlining the equivalent
    substitutions, exact output and undef/overload behavior pass in both source
    and compiled-XS tests. Twenty-five alternating 500,000-call samples for
    ordinary text measured 175.998 ms through the previous fallback and
    135.123 ms with the direct path (1.30x). This source-only helper result
    does not establish the universal 50x objective. The complete Docker gate
    passed 285 test files / 25,229 tests with all four lib/ metrics at 100.0%
    (51,571 detail rows; no stale uncoverable annotations).

  - Problem 48: rerun the matched 100-skill/20-saved-nav route against the
    recorded v5.88 baseline. Current returned HTTP 200 with the exact same
    18,792-byte hash and measured 0.420060 versus 135.065870 ms/request
    (321.54x for this fixture only). The cumulative result is not attributed
    to one change and does not prove the all-subroutine 50x requirement.

  - Problem 48: optimize the common one-section legacy bookmark parser path by
    avoiding the multi-section split list when no legacy or Markdown separator
    is present. A Docker red-first regression covered trimming, multiline body
    preservation, undefined input, extended CODE keys, and unknown sections. Six representative
    parser inputs retain exact result parity; nine alternating 500,000-call
    Docker samples measured 1.87x for a simple TITLE/HTML section. This is a
    helper-specific result and does not establish the universal 50x target.

  - Problem 48: matched v5.88 against current source using 100 installed
    skill-nav roots and 20 saved nav fragments. Both responses were byte-identical;
    nine samples of 100 requests measured 135.065870 versus 1.136279 ms/request
    (118.87x for this fixture). This is route-level evidence only; it does not
    prove 50x for every subroutine or platform.

  - Problem 48: keep aggregate skill-nav cache invalidation explicit when an
    optional watcher reports a setup failure but lacks a last_error method and
    also fails to close. The dispatcher now reports an explicit unknown close
    failure without calling an unsupported method. A red-first Docker regression
    reproduced the crash; the focused dispatcher test now passes 333 assertions.

  - Problem 48: reject unavailable skill-nav page lists before comparing
    reference addresses, avoiding an uninitialized-value warning while keeping
    malformed page snapshots on the ordinary rendering path. The Docker web
    coverage regression now clears its prior saved-nav snapshot between mocked
    fallback cases and verifies the warning-free path. Literal skill-nav
    attributes remain escaped while being assembled without a bare-variable
    interpolation pattern. Focused web and attribute-security suites pass.

  - Problem 48: avoid running three malformed-icon repair regexes for ordinary
    saved-page text without U+FFFD. The focused Docker PageStore/web suites pass
    with source-only and compiled-XS paths. A matched 100-skill/20-saved-nav
    route benchmark retained HTTP 200 and exact body hash; the latest rerun
    measured v5.88 at 69.971821 ms/request, source-only at 0.717950 ms/request
    (97.46x), and compiled XS at 0.773010 ms/request (90.52x). The compiled
    route sample was slightly slower than source-only and does not establish
    an XS route gain. These are route-fixture results, not an all-routine 50x
    claim.

  - Problem 48: route long, densely escaped PageDocument text through the shared
    XS body escaper while keeping a fast plain-scalar return and the existing
    Perl path for short or sparse text. Seven alternating Docker microbenchmark
    samples preserved exact output; the compiled XS path measured 11.29x for a
    384-character dense fixture, while plain/short cases stayed near parity.
    Source-only fallback samples ranged from 0.85x for short quote-heavy text
    to 1.34x for dense input. This is helper-specific, not a route or
    all-subroutine 50x claim.

  - Problem 48: route long, densely escaped PageDocument text through the shared
    XS body escaper while keeping a fast plain-scalar return and the existing
    Perl path for short or sparse text. Seven alternating Docker microbenchmark
    samples preserved exact output; the compiled XS path measured 11.29x for a
    384-character dense fixture, while plain/short cases stayed near parity.
    This is helper-specific, not a route or all-subroutine 50x claim.

  - Problem 48: keep the bounded skill-nav route inventory useful when a
    dashboard has more than 64 installed skill roots. The FIFO now retains up
    to 128 roots and stores validated relative segments with each stable
    inventory, avoiding repeated path splitting while preserving the path
    boundary checks. A 100-skill Docker fixture retained exact response bytes,
    reduced recursive scans from 90,300 to 100, and measured 3.794620 versus
    7.907050 ms/request (2.08x in sequential controls). This remains a narrow
    incremental gain, not the all-subroutine 50x objective. The focused
    dispatcher and benchmark suites pass 295 assertions. The final full Docker
    coverage gate passed 285 files / 24,976 tests with all four lib/ metrics at
    100.0% (50,889 detail rows; no stale uncoverable annotations).

  - Problem 48: cache the directory inventory for unchanged skill-nav trees.
    High-resolution fingerprints cover every visited directory and cause an
    immediate rescan after route additions, removals, or nested-tree changes.
    A 100-skill Docker benchmark retained exact response bytes, cut recursive
    scans from 45,150 to 50, and measured a 1.26x incremental gain. The
    all-subroutine 50x objective remains open.

  - Problem 48: cache unchanged literal skill-nav page documents in a bounded
    per-dispatcher FIFO. Every route still resolves its path and checks the
    high-resolution file fingerprint; dynamic and structured pages remain
    uncached. Red-first Docker tests cover edits and route additions/removals.
    Two paired 100-skill route comparisons retained byte-identical output and
    measured a 1.2665x incremental gain over the uncached current-source
    control. This does not meet the all-subroutine 50x target.
    The follow-up full Docker coverage gate passed 285 files / 24,950 tests
    with all four coverage metrics at 100.0% across lib/ (50,804 detail rows),
    including malformed FIFO eviction, stale-dispatcher class, directory
    invalidation, unstable scan, and inventory-boundary cases.

  - Problem 48 portable configuration freshness now shares the installed-skill
    inventory between watch-path creation and exact-content signature
    validation. A red-first Docker regression reproduced four skill scans on
    first cache fill and two on each cache hit; the fix reduces these to two
    and one while retaining the post-merge freshness scan. The focused web
    suite passes 507 assertions. Matched 100-request NYTProf runs preserve
    HTTP status, body size, and SHA-256; signature inclusive time changed from
    18.2 ms to 15.6 ms under profiling on the empty-skill fixture. This is not
    a route-level or v5.88 speedup claim; the all-subroutine 50x target remains
    open. The complete Docker coverage gate passes 285 test files and 24,840
    tests with 100.0% statement, branch, condition, and subroutine coverage
    across C<lib/> (50,496 detail rows; no stale uncoverable annotations).
  - Expand hosted cross-platform verification to Linux, macOS, and Windows
    across x64 and ARM64 runner hardware. The full suite runs on Linux/macOS
    x64 and ARM64 and Windows x64; Windows ARM64 runs a focused native watcher
    test because its hosted Perl is below the supported 5.38 minimum.

5.96  2026-10-16

  - Add native filesystem event acceleration for macOS and Windows alongside
    Linux inotify, while retaining the content-hashing portable validator.
    Cross-platform CI now requires a real native watcher and writes a watched
    file to prove invalidation on Linux/macOS/Windows x64 and arm64 runners.
    Focused Linux Docker watcher tests pass; hosted macOS and Windows execution
    is pending the workflow run.
    Follow-up watcher tests now exercise explicit and empty native cleanup
    errors, malformed FSEvents/Win32 handles, loader failures, and false
    FSEvents stop returns. The focused Docker watcher suite passes 209 tests;
    its isolated Watch.pm report is 100.0% for statement, branch, condition,
    and subroutine coverage. Hosted macOS/Windows runtime results remain pending.
    The six-leg workflow also asserts the actual OS and CPU architecture before
    testing, preventing a mislabeled hosted runner from satisfying a matrix leg.
  - Fix path-ancestry identity checks to use the platform's root separator and
    case-tolerance rules for the root volume. A red-first Windows-path
    regression reproduced false negatives for backslash paths, case variants,
    and drive-root descendants; focused PathIdentity/PathRegistry assertions
    verify component boundaries and per-drive case-sensitivity lookup.

  - Keep configuration-snapshot reuse available when native filesystem event
    APIs are unavailable. A portable validator fingerprints layered directory
    metadata and SHA-256 hashes the exact global, project, and installed-skill
    config files, so same-size in-place edits and file creation/removal are
    observed before reuse. Docker regressions force the no-native-backend path
    and verify cache hits plus invalidation. A runtime-rejected native watcher
    now seeds this same portable cache after a fresh merge. Linux inotify
    remains an optional faster hint; macOS and Windows use the portable path.
    The cross-platform workflow now compares automatic and forced-portable
    config caching on every Linux/macOS/Windows x64/arm64 runner. A reproducible
    paired Docker run preserved the exact 12,864-byte status response and
    measured 0.336020 ms/request portable versus 0.117619 automatic (2.86x
    slower in this Linux fixture); macOS/Windows latency remains unmeasured.
    The complete Docker coverage gate passed 285 files / 24,691 tests with
    statement, branch, condition, and subroutine coverage at 100.0% across
    C<lib/> (50,154 detail rows; no stale uncoverable annotations).
    This closes the cache-availability gap only; Problem 48's all-subroutine
    50x performance objective remains open.

5.95  2026-10-16

  - Add GitHub Actions compatibility coverage for Linux, macOS, and Windows
    runner images on x64 and arm64. Each matrix leg installs project
    dependencies and runs the complete Perl test suite; the Windows arm64 leg
    uses the Perl provided by its native GitHub runner image. This workflow is
    added but has not yet been observed completing on GitHub-hosted runners.

  - Problem 48 profiled a warmed saved-page route with 20 saved and 100 skill
    nav fragments. v5.88/current medians were 53.166/7.759 ms per request
    (6.85x) with identical HTTP output (18,792 bytes and the same SHA-256).
    NYTProf located repeated skill-page lookup/read work, but no cache or
    parallel loader was adopted because file freshness and security costs have
    not been justified by a measured material route gain. This workload remains
    well below the all-routine 50x target.

  - Problem 48 full Docker coverage now passes 285 test files / 24,650 tests
    with statement, branch, condition, and subroutine coverage all at 100.0%
    (49,981 detail rows; no stale uncoverable annotations). The coverage audit
    extracted the collector inventory unchanged predicate and tests all eight
    input combinations directly, alongside reconciliation regressions.

  - Problem 48 skill-nav rendering now uses PathRegistry's bulk installed-skill
    layer entries for top-level nav pages, avoiding one layer resolution per
    installed skill while nested skill layers keep normal lookup semantics.
    Docker tests verify exact page order/content and the legacy fallback. Two
    alternating 100-request / 31-sample route pairs returned the same 14,942-
    byte response hash; bulk medians were 3.773839/3.644860 ms/request versus
    4.254751/4.386990 for the former loop and reduced measured layer resolutions
    from 155,000 to zero. This fixture-specific result does not prove the full
    all-subroutine 50x target.

  - Problem 48 literal installed-skill nav fragments now bypass PageRuntime
    preparation, matching the saved-nav path; fragments containing TT or
    structured page data still use the runtime. A red-first web regression
    verifies static skill nav skips preparation while dynamic TT still renders.
    The 20-saved/50-skill Docker fixture preserved its exact 14,942-byte
    response hash and measured 3.553019 ms/request before versus 3.153169 after
    across 21 runs of 100 requests (1.13x). This route-specific result does not
    prove the 50x all-subroutine objective.

  - Problem 48 saved-page rendering now records the deepest matching dashboard
    layer for each nav fragment and loads that file directly instead
    of searching every page root. Literal HTML fragments use a bounded
    128-entry / 4 MiB per-store cache keyed by high-resolution file identity;
    containment is checked on every hit, symlinks remain rejected, and edits
    are visible on the next request. Dynamic TT and structured nav retain the
    regular parser/render path. Docker tests cover layer precedence, same-file
    updates, symlink replacement, cache bounds, and exact route output. Custom
    page resolvers remain authoritative. For 20 fragments the matched
    v5.88/current medians were 9.256661/0.699711 ms per request (13.23x); the
    no-cache control was 1.406310 ms with an identical response hash. These are
    route-fixture results, not proof of the requested all-subroutine 50x target.
    The later complete coverage gate result is recorded in the final Problem 48
    follow-up below.

  - Keep the saved-nav direct PageStore loader conditional on a blessed page
    store. The full suite exposed an unblessed path-registry fixture that must
    use the compatibility resolver path; the previous method probe died before
    navigation could render. A red full-suite assertion was reproduced in
    Docker, the error is now surfaced in its test diagnostic, and the complete
    `t/105-web-app-coverage-2.t` file passes. The later full gate result is
    recorded in the final Problem 48 follow-up below.

  - Avoid a duplicate saved-path containment assertion on literal-nav cache
    misses: the secure file-open boundary remains the single check, while cache
    hits continue to validate containment on every request. A confirmatory
    Docker A/B run measured 9.080269 ms/request on v5.88 and 0.712309 ms/request
    currently (12.75x), with identical HTTP 200 output (11,092 bytes and the
    same SHA-256). The fixture recorded 44,100 current containment checks versus
    88,200 on v5.88. This route result does not meet the all-subroutine 50x goal.
    The latest full coverage gate passed 284 files / 24,515 tests with all four
    `lib/` metrics at 100.0% (49,770 detail rows; no stale annotations).

  - PageStore validation now returns canonical, safe, single-level `nav/*.tt`
    ids through a narrow fast path; prefixed, nested, unusual, reference, and
    traversal ids retain the established normalization and validation path.
    Red-first Docker regressions cover both paths, and the new reproducible
    benchmark compares exact results before timing. Nine 500,000-call samples
    measured this helper 2.45x faster for common nav ids. A reversed-order
    20-fragment route confirmation preserved HTTP output and measured 9.553111
    ms/request on v5.88 versus 0.682609 ms/request currently (13.99x). The
    all-subroutine 50x target remains open. After adding the fast-path
    fallback-condition cases, the complete Docker gate passed 285 files / 24,543
    tests with statement, branch, condition, and subroutine coverage all at
    100.0% across `lib/` (49,777 detail rows; no stale annotations).

  - Skill-navigation rendering now resolves each skill's home-to-leaf layer
    list once per page and reuses it for both file lookup and metadata. Within
    a request's existing installed-skill inventory, validated name segments
    and directory membership are reused while directory presence and disabled
    markers remain checked on every lookup. A red-first Docker regression
    measured one layer resolution instead of two per loaded skill page; the
    paired 50-skill route medians were 4.7143/4.2943 ms with identical HTTP
    output (1.10x). Against v5.88, this current 50-skill route measured 8.01x;
    neither result proves the all-subroutine 50x objective.

  - Reuse each skill's already-resolved layer list while enumerating its nav
    fragments as well as loading them. A red-first Docker regression showed
    three lookups for one skill with two nav fragments; the revised route uses
    one. Against a control restoring per-fragment lookup, 50 skill fragments
    measured 4.576519/4.087360 ms per request (1.12x), with 210,000/105,000
    resolutions and byte-identical HTTP 200 responses. This remains fixture-
    specific and does not meet the all-subroutine 50x objective. The subsequent
    full Docker gate passed 285 files / 24,576 tests with statement, branch,
    condition, and subroutine coverage all at 100.0% across `lib/` (49,841
    detail rows; no stale uncoverable annotations).

  - Skill nav files containing ordinary HTML/TT text now bypass the structured
    instruction parser, while recognized previous-format and modern page sections and
    raw URL bookmarks retain their existing handling. Red-first Docker tests
    check that raw text skips parsing and structured fragments keep their page
    fields. In the matched 50-skill fixture, skipping parsing measured 1.14x
    against a control forcing the prior parser path, with byte-identical output.
    The all-subroutine 50x objective remains open. The subsequent full Docker
    gate passed 285 files / 24,593 tests with every library metric at 100.0%
    (49,880 detail rows; no stale uncoverable annotations).

  - Problem 48 status requests now cache the normalized indicator-collector
    projection inside the watcher-validated config snapshot, avoiding one
    repeated projection per request. The checker-filter environment variable is
    part of the snapshot signature, so changing it rebuilds the selected jobs.
    Red-first Docker tests proved repeated projection before the fix and one
    projection after; both filter-signature behavior and exact 12,864-byte
    response parity are covered. Two reverse-order 21-sample A/B pairs measured
    a 1.29x status-route improvement (cached medians 0.113299/0.111630 versus
    no-cache 0.143151/0.148051 ms/request), with projection calls reduced from
    2,103 to 1. This remains a targeted route gain, not the all-subroutine 50x
    result. The full Docker coverage gate passed 284 files / 24,419 tests, all
    four `lib/` metrics at 100.0% (49,525 detail rows; no stale annotations).
    `perldoc perlsec` was attempted after the gate, but the dev container lacks
    a loadable POD formatter, so that command did not pass.

  - Problem 48 status synchronization now reuses the canonical signature for
    collector jobs held in a watcher-validated config snapshot; runtime
    indicator roots are still read and included each sync. Malformed explicit
    signatures fail clearly. Red-first Docker tests verify signature reuse,
    invalid-input errors, and unchanged HTTP output. Two reverse-order
    21-sample A/B pairs measured 1.19x faster (cached medians 0.105081/0.103180
    versus 0.123909/0.124791 ms/request), with signature encodes reduced from
    2,103 to 1 and the same 12,864-byte response hash. This is a targeted route
    result, not proof of the all-subroutine 50x objective.

  - Add one-pass XS escaping for quoted HTML attributes. Scalar inputs now
    escape ampersands, angle brackets, and both quote characters in one native
    scan; source-only builds and overloaded references retain the Perl path.
    Plain Perl fallback attributes of at least 12 characters can bypass five
    substitution scans when no escapable character is present. The benchmark
    now uses an independent pure-Perl reference rather than inadvertently
    timing XS in the baseline. Docker measurements show 1.63x for a routine nav
    ID in the source-only path and 1.80x with XS loaded; quote-heavy text is
    4.20x with XS. Short quote-heavy source-only text stays on the substitution
    path; the full helper comparison was 0.89x on that tiny case, so this
    tradeoff is workload-specific. These helper-level results claim no
    whole-route or all-subroutine 50x speedup.

  - Harden literal-nav cache refresh and eviction against malformed cached
    entries, which previously could crash while dereferencing a non-hash value.
    PageStore now treats an invalid old entry as having no reusable body and
    rejects a path that changes from a regular file to a non-regular object
    before the initial fingerprint. Docker regressions cover malformed/stale
    entries, file replacement races, an absent parsed page id, and invalid root
    snapshots. The focused PageStore/web tests pass 646 assertions. The first
    full run after these additions passed 284 files / 24,495 tests but reported
    99.9% branch and 99.8% condition coverage. After the remaining condition
    was simplified, the complete gate passed 284 files / 24,513 tests at
    100.0% for statement, branch, condition, and subroutine metrics across
    `lib/` (49,769 detail rows, no stale uncoverable annotations). This closes
    the coverage gate, not the all-subroutine 50x performance objective.

  - The next full report isolated the remaining condition gap to a false
    assignment expression whose replacement ID is guaranteed non-empty by
    validation. Rewrote it as an explicit false-value assignment guard and
    reran the focused Docker instrumentation: PageStore condition coverage is
    now 100.0%, with 646 focused assertions passing. The subsequent complete
    Docker gate passed at 100.0% for all four library metrics.

  - Fixed saved-page containment comparisons on POSIX so literal backslashes
    remain filename characters rather than being normalized as separators.
    Windows-only case/separator normalization preserves the platform-specific
    comparison rules while removing two unnecessary substitutions from the
    POSIX hot path. A red-first Docker regression confirmed that an outside
    sibling could previously be mistaken for a contained path; the test now
    rejects it. A fresh exact-output route comparison remained 5.97x versus
    v5.88 for this fixture (3.777349 versus 0.632660 ms/request); this is not
    attributed to the containment correction. Problem 48's all-subroutine 50x
    goal remains open.

  - Problem 48 saved-page requests now reuse the shared-navigation filename
    inventory while each layered nav directory's high-resolution identity and
    timestamps remain unchanged. Added/removed fragments invalidate the
    inventory; fragment contents remain fresh per request, and incomplete
    scans are not cached. Red-first Docker tests cover mutation, malformed
    cache data, missing roots, and visible filesystem errors. Exact-response
    route comparison measured 0.651679 ms/request cached versus 0.667362
    uncached (1.024x); this modest route gain does not meet the all-subroutine
    50x objective. Saved-page lookup also no longer repeats the candidate
    containment check immediately before the secure open; symlink and
    containment validation still happens at the open boundary. Seven alternating
    route pairs measured 1.11x versus the duplicate-check control with exact
    response parity. The full Docker gate passed 283 files / 24,353 tests with
    all four library metrics at 100.0% (49,493 detail rows).

  - Problem 48 saved-page candidate lookup now snapshots the ordered dashboard
    roots lazily for one request, then resolves a fresh list on the next
    request; direct PageStore calls remain fresh. Tests prove one candidate
    root lookup per web request and root-list changes between request scopes.
    The expanded exact-output benchmark shows no measurable route improvement
    at five nav fragments, but 1.04x at ten (0.792992 vs 0.824606 ms/request),
    with 1,980 root lookups reduced to 180. This remains workload-specific and
    does not meet Problem 48's all-subroutine 50x objective. The full Docker
    gate passed 283 test files / 24,372 tests with all four library metrics at
    100.0% (49,511 detail rows, no stale uncoverable annotations).

  - Problem 48's full Docker coverage gate was rerun after adding regression
    cases for unavailable/rejected filesystem watchers, cache hit/miss states,
    malformed skill-layer inventories, and home/project ancestry boundaries.
    All 283 test files and 24,307 assertions passed; statement, branch,
    condition, and subroutine coverage are each 100.0% across lib/ (49,396
    detail rows, no stale uncoverable annotations). This closes the current
    coverage gap only; the all-subroutine 50x performance objective remains
    open.

  - Problem 48 status-route config snapshots now remain reusable across
    requests: `secure_dir_permissions` checks directory mode before chmod,
    avoiding redundant `ATTRIB` events that invalidated the inotify cache.
    The watcher now limits ancestry monitoring to home/project runtime-layer
    discovery rather than watching unrelated ancestors to `/`. Docker tests
    cover native cache reuse, config-change invalidation, permission errors,
    and no-op chmod behavior. A 50-indicator byte-identical route workload
    measured 381.04x versus v5.88 (61.043739 vs 0.160201 ms/request); this is
    a route-specific result and does not satisfy the all-subroutine target.

  - Problem 48 saved-page route optimizations now avoid canonical-instruction
    serialization for literal raw navigation and share dashboards-root realpath
    resolution only for the duration of one request. Dynamic navigation still
    receives runtime preparation, every candidate page path still gets its
    containment check and secure open, and the next request resolves roots
    afresh. The exact-output five-static-nav `/app/welcome` benchmark measured
    5.58x over v5.88 (3.758640 versus 0.673630 ms/request). This does not meet
    the all-subroutine 50x objective. The full Docker coverage gate passed 283
    files / 24,251 tests with all four `lib/` metrics at 100.0% (49,149 detail
    rows; no stale annotations); the required web security trio passed 466 tests.

  - Problem 48 Config::load_global now shares one runtime-layer discovery
    snapshot across its global-file and installed-skill lookups, scoped to that
    load and nested inside any existing request scope. A red-first Docker test
    observed five ancestry walks before the change and one afterward; another
    proves a later load sees a runtime layer created after the first call.
    Exact-output alternating Docker measurements across four runtime layers
    and 25 skill configs improved from 0.3175 to 0.2106 ms/load (1.51x). This
    workload-specific result does not satisfy the all-subroutine 50x target.
    The same load-local snapshot now covers api_registry's skill and global
    API reads. Its red-first test observed four ancestry walks before and one
    after; a four-layer/25-skill alternating Docker comparison preserved exact
    normalized API output and improved median time from 0.3728 to 0.2638
    ms/load (1.41x). Later API loads still detect newly created layers. The
    clean full Docker gate then passed 281 files / 24,176 tests with all four
    lib/ coverage metrics at 100.0% (49,037 detail rows; no stale annotations).

  - Problem 48 scoped runtime-path follow-up: cache the selected
    .developer-dashboard/.d2 directory name only inside the request's
    runtime-layer operation scope, and skip ancestor discovery when the exact
    cwd equals home. Red-first Docker tests verified an in-scope snapshot stays
    consistent while a later operation sees a newly created layer, and exact
    home cwd avoids project-root/canonical-ancestry helpers. Invalid parent
    paths now fail with a clear diagnostic. In a scoped
    50-collector Config::load_global benchmark, median time fell from 0.146348
    to 0.084262 ms/load (1.74x), with identical canonical output and next-load
    external-edit visibility. The standalone result remains 1.09x slower than
    v5.88; route output stayed byte-identical and route timing was noisy. This
    does not close the all-subroutine 50x objective.

  - Problem 48 config-merge follow-up: when one global JSON layer is the only
    contributor, avoid recursively merging the decoded config into an empty
    hash; when repo config is empty, return the already-normalized global
    snapshot directly. Duplicate collector/provider identities still resolve
    with the established last-entry merge semantics, while layered files,
    skill fragments, and repo overlays continue through the normal merge path.
    Red-first Docker coverage caught the redundant merge (one call before,
    none after for a unique single-layer fixture); duplicate-identity and
    request freshness checks pass. The focused web/config/benchmark suites
    passed 630 tests. A 50-collector status route comparison kept exact
    12,864-byte response parity; its 0.258 ms/request result was within noise
    of the prior 0.260 ms run, so no incremental route gain is claimed. The
    broader v5.88 route result remains workload-specific. A fresh matched
    non-profiled 21-run comparison measured 72.415810 ms/request on v5.88 and
    0.249081 ms/request currently (290.73x) with byte-identical 12,864-byte
    responses; this timing variation is not attributed to the config-merge
    change and does not prove the all-subroutine 50x objective.

  - Problem 48 PathRegistry metadata follow-up: retain the canonical JSON
    scalar for the latest exact runtime-root/application-name pair, avoiding
    redundant serialization. File and directory freshness/security checks
    still run on every call; only string generation is reused. Red-first Docker
    tests confirmed repeated identical calls encode once, changed roots or
    application names regenerate the string, and malformed cache entries become
    cache misses instead of leaking uninitialized-value warnings. The focused
    PathRegistry suite passed 317 assertions and the status route retained its exact 12,864-byte
    response/hash. Timing moved from 0.249081 to 0.247939 ms/request, within
    run-to-run noise, so no route speedup is claimed. Problem 48's all-routine
    50x objective remains open.

  - Problem 48 state-root permission follow-up: remove duplicate hardening calls
    from state_root and _state_root_for_layer; _write_state_metadata remains
    responsible for creation and permission checks. Red-first Docker assertions
    verified one metadata check per root and preserved all_paths output. Focused
    PathRegistry/status tests passed 361 assertions. NYTProf retained the exact
    12,864-byte status response and showed _ensure_state_dir calls falling from
    400 to 300 and exclusive time from 2.82 ms to 0.523 ms per 100 profiled
    requests. Unprofiled route timing (0.250821 vs 0.249081 ms/request) was
    within noise; no route gain is claimed. An indicator-job shortcut measured
    0.858x and was rejected. The subsequent clean Docker coverage gate passed
    281 files / 24,182 tests with statement, branch, condition, and subroutine
    metrics at 100.0% across lib/ (49,039 detail rows; no stale annotations).
    Problem 48 remains open because the all-subroutine 50x target is unproven.

  - Problem 48 matched status-route recheck after the state-root follow-up:
    v5.88 measured 60.881879 ms/request and current source 0.342300
    ms/request (177.77x) across 21 runs of 100 requests with 50 indicators.
    Both returned HTTP 200 and the same 12,864-byte SHA-256 response. This is a
    workload-specific route result, not evidence that every subroutine is 50x
    faster. A later five-run/20-request exact-source confirmation measured
    57.779455 ms/request versus 0.104547 ms/request (552.67x), preserving the
    same response hash; this shorter sample does not replace the primary
    comparison or prove the all-subroutine target.

  - Problem 48 stable collector-sync follow-up: add an optional Linux inotify
    hint through typed FFI::Platypus libc calls. Matching event-free watch sets
    skip repeated directory and file-signature scans only after a portable
    validation; any event, setup/read error, or unsupported/missing optional
    backend retains the portable validation path. Native-enabled Docker tests
    and exact inventory/freshness checks pass. A 21-sample benchmark of the
    stable 50-indicator sync helper measured 0.001600 ms with inotify versus
    0.256500 ms with the portable validator (160.33x). This narrowly scoped
    result is not a v5.88 or whole-dashboard comparison; Problem 48's
    all-subroutine 50x target remains open. The latest full Docker gate passed
    281 files / 24,151 tests with 100.0% statement, branch, condition, and
    subroutine coverage across lib/ (48,963 detail rows; no stale annotations).

  - Problem 48: reuse constructed indicator status-file paths while the exact
    ordered roots and discovered inventory remain unchanged. High-resolution
    file metadata is still checked on every request, preserving visibility of
    external changes. A red-first Docker test reproduced 24 path joins for 12
    entries across two validations; the regression now verifies only 12 joins.
    A 21-sample, 50-collector `/system/status` comparison preserved exact
    12,414-byte responses and measured 72.341 ms per 100 requests without the
    path-string cache versus 66.347 ms with it (1.09x). This incremental
    workload result does not meet Problem 48's all-subroutine 50x objective.
    Follow-up cache tests cover absent/stale signatures and malformed path
    lists. The full Docker gate passed 277 files / 23,938 tests with 100.0%
    statement, branch, condition, and subroutine coverage across lib/ (48,462
    detail rows; no stale uncoverable annotations).

  - Problem 48 delivery follow-up: make the packaged-install regression for
    empty machine-IP discovery independent of test order and host/container
    networking by clearing the worker-local address cache before asserting
    the empty-discovery result. The blank-container cpanm run exposed the
    stale-cache assumption; the focused Docker test now passes all 286
    assertions. The 5.95 tarball installed successfully in a blank Docker
    environment with cpanm and its bundled suite passed 277 files / 18,197
    tests; the documented blank-environment integration run also passed.

5.94  2026-10-15

  - Problem 48 status-route follow-up: the stable-metadata validator now builds
    each discovered status-file path once and shares its existence result with
    active collector validation, removing a duplicate path-join pass while
    retaining high-resolution file freshness checks. A red-first Docker test
    reproduced 24 joins for 12 entries before the change and verifies 12
    afterward. In the matched 50-collector workload, current source measured
    0.437 ms/request with identical 12,687-byte bodies; a fresh v5.88 baseline
    measured 34.307 ms/request (78.50x for this route only). The all-subroutine
    50x objective remains open.
    Full Docker coverage then passed statement, branch, condition, and
    subroutine metrics at 100.0% (48,438 detail rows, no stale uncoverables).
    The post-gate perlsec review and required web/security regression set also
    passed; the latter covered 622 assertions across four test files.

  - Problem 48 status-route follow-up: reuse the exact serialized JSON body and
    skip rebuilding its in-memory payload only after collector sync validates
    that configuration, indicator files, and directory inventory are stable.
    A changed indicator forces fresh payload generation. Red-first Docker tests
    verify exact response parity, skipped payload work, and invalidation. In a
    matched 50-collector Docker benchmark, the route returned identical 12,687-
    byte bodies at 34.257 ms/request on v5.88 and 0.597 ms/request currently
    (57.38x). This route result does not complete the all-subroutine 50x goal.
    The subsequent clean Docker gate passed 277 files / 23,871 tests with
    100.0% statement, branch, condition, and subroutine coverage across lib/
    (48,324 detail rows, no stale uncoverable annotations).

  - Problem 48: share one ordered indicator-root snapshot and one decoded-file
    cache across each collector sync, then discard it so the next request sees
    external changes. Red-first Docker coverage verifies one root resolution
    and one physical read per indicator, next-sync freshness, and collector
    retirement invalidation. For 50 collectors, 15 alternating Docker runs
    measured 9.203 ms/request before and 2.689 ms/request after (3.42x) with
    exact response bytes. The 50x all-subroutine objective remains open.
  - Problem 48 follow-up: when a collector sync has one indicator root, reuse
    its already-loaded effective record as the local record and skip the
    impossible inherited-root lookup. A red-first Docker regression checks
    that neither redundant lookup runs; changed-state visibility and exact
    status output remain covered. Fifteen alternating status-route runs with
    50 collectors measured 3.51x over the legacy path with exact byte parity.
    This incremental gain does not meet the 50x all-routine objective.
  - Problem 48 baseline verification: with the same Docker image, 50 collector
    records, and exact 12,687-byte `/system/status` response, the v5.88 tree
    measured 35.090 ms/request and the current checkout measured 1.624
    ms/request (21.61x). This direct route comparison remains below 50x and
    does not establish the every-subroutine target.
  - Problem 48 follow-up: cache raw indicator JSON bytes across sync calls only
    while a high-resolution file signature, collector configuration, and
    directory inventory remain unchanged. Stable status requests still verify
    file metadata and decode fresh values; changes, additions, removals, and
    collector retirement fall back to reconciliation. Red-first Docker tests
    cover those freshness boundaries and caller-mutation isolation. The same
    50-collector status workload measured 0.670 ms/request versus the v5.88
    baseline of 35.090 ms (52.37x) with exact 12,687-byte response parity.
    The final clean Docker gate passes 276 files / 23,851 tests with 100.0%
    statement, branch, condition, and subroutine coverage across lib/ (48,283
    detail rows; no stale annotations). This exceeds 50x for this route only;
    the every-subroutine goal remains open.


  - Problem 48: cache compiled scalar Template Toolkit blocks across web requests
    while constructing fresh documents per render; keep machine-address results
    for five seconds and scope canonical path/runtime-layer snapshots to a web
    request. Red-first Docker tests cover compile reuse, address expiry, request
    cleanup, live include and runtime-layer freshness. In 15 alternating Docker
    route runs with identical 9,573-byte responses, the median fell from 1.673
    ms at v5.88 to 0.497 ms currently (3.37x). This is route-specific evidence;
    the 50x all-routine target remains open.
  - Problem 48 follow-up: reuse parsed scalar Template Toolkit syntax trees
    within a web request while creating a fresh document per render. The cache
    is bounded and request-local; exact output parity includes dynamic stash,
    custom META, and live INCLUDE behavior. Fair request-scoped Docker
    benchmarks with five renders per request measured 1.38x for five identical
    sources and 0.97x for five distinct sources. Problem 48's
    50x-per-subroutine goal remains open.
  - Problem 48 follow-up: fix the XS dispatch regression for long inputs whose
    first escape is near the beginning but whose early escape density is low.
    A red-first regression reproduced the direct byte loop on this workload;
    the dispatcher now chooses chunk-copy unless a long input has at least
    four escapes in its first 16 characters. Compiled-XS Docker tests passed
    all 44 assertions, including exact 1 MiB byte/UTF-8/taint parity. The
    1 MiB prefix-sparse workload measured 2.65x helper and 1.69x complete
    highlighting speedups versus Perl. The full Docker gate passed 275 files /
    23,738 tests with 100.0% statement, branch, condition, and subroutine
    coverage across lib/ (47,889 detail rows; no stale uncoverables). The
    50x-per-subroutine objective stays open.
  - Problem 48: compile the shared HTML body escaper as XS for ordinary scalar
    input, while preserving the Perl fallback for source-only runs and
    overloaded references. A staged Docker build passes exact byte, Unicode,
    NUL, UTF-8/taint-flag, and output parity checks. Against the Perl
    reference, the installed XS backend measured 12.63x for a 1 MiB body and
    9.53x for full instruction highlighting. This is a measured routine gain,
    not a claim that every dashboard subroutine is 50x faster.
  - Problem 48: exercise both XS-dispatch outcomes in source-only coverage and
    provide MakeMaker fixtures with the required XS source and lightweight
    version module. The focused HtmlEscape coverage gate now reaches 100.0% on
    all four metrics. The complete Docker suite then passed 274 files / 24,063
    tests with 100.0% statement, branch, condition, and subroutine coverage
    across lib/ (47,861 detail rows; no stale uncoverable annotations).
  - Problem 48: add 1 MiB exact-output coverage for the compiled escaper and
    benchmark a byte-indexed C replacement table. The table candidate was
    slower than the production byte loop in the staged 21-sample comparison
    and was not adopted.
  - Problem 48: route no-escape text around XS, dense escapes through the
    direct native loop, and sparse escapes through native chunk-copy. Red-first
    Docker tests cover all dispatch branches and 1 MiB dense, sparse, and
    plain Unicode parity. In 21 rotated staged Docker samples at 1 MiB, the
    helper measured 12.40x, 3.08x, and 1.02x over Perl for dense, sparse, and
    plain inputs; full instruction highlighting measured 9.02x, 1.74x, and
    1.00x, respectively. Problem 48's all-subroutine 50x goal remains open.
  - Problem 48 coverage follow-up: add source-only tests for both search-order
    comparator outcomes and the Perl fallback's undefined-input branch. With
    Docker init enabled for child reaping, the final full gate passed 274 files
    / 24,088 tests and 100.0% statement, branch, condition, and subroutine
    coverage across lib/ (47,881 detail rows, no stale uncoverables).
  - Problem 48: precompute indicator ordering fields for lists of at least 32
    entries, avoiding repeated comparator method calls and hash lookups. The
    same-input and direct tuple-comparator regressions preserve priority,
    collector-order, and name tie-breaking; full-list Docker
    measurements show 1.04x at 50 records, 1.14x at 1,000, and 1.06x at
    5,000. A final 30-pair isolated 5,000-record sort benchmark measured
    16.240 ms for the legacy comparator and 10.763 ms for decorated sorting
    (1.51x), with exact order parity. The overall 50x-per-subroutine goal
    remains open.
  - Fix the root-only unreadable-auth-record test fixture by assigning its
    temporary runtime directories and record to the dropped test uid. The test
    now reaches the intended file-open denial after directory hardening.
  - Keep optional Problem 48 benchmark engines (Inline, MCE::Map, SPVM, and
    SPVM::Digest::SHA) out of generated install-time prerequisites. Clean
    tarball installation must not be blocked by benchmark-only toolchains or
    third-party engine test failures.
  - Problem 48: skip Template Toolkit setup for static bookmark bodies that
    contain no default C<[%> opener. A red-first Docker regression verifies
    unchanged Unicode HTML and proves context, include-root, and processor setup
    are bypassed. A 21-pair Docker microbenchmark preserves exact output and
    measures 0.06436 ms through Template Toolkit versus 0.00080 ms on the
    static path (80.66x for this render case only); the all-routine 50x
    objective remains in progress.
  - Repair the blank-environment installer verifier: derive the expected
    release version from the extracted META.yml before using the legacy
    literal-module fallback. The public module delegates $VERSION to the
    lightweight version module, so reading only Dashboard.pm incorrectly
    rejected the built tarball before cpanm ran. Add red-first tests for
    metadata precedence, legacy fallback, and safe helper loading; document
    the verification behavior. The Problem 48 performance objective remains
    in progress and is not represented as complete by this gate repair.
  - Keep the coverage gate's Devel::Cover serializer stamp beside its database,
    not inside it, so `cover -delete` and report collection no longer mistake
    gate metadata for an invalid coverage database. The old in-database marker
    is verified and migrated. Red-first Docker coverage-gate tests pass.
  - Problem 48: give direct Dancer backend methods the same request-local
    runtime-layer and installed-skill inventory scopes as Web::App::handle.
    A red-first Docker PSGI test reduces repeated ancestry discovery from five
    calls to one within a request and confirms the scope is discarded afterward.
    Eleven paired runs of 100 actual `/system/status` requests preserved exact
    response bodies and measured 0.5984 ms without the scope versus 0.5696 ms
    with it (1.05x). Also skip state-directory chmod only when its observed
    permission/special-bit mask is already exactly 0700. A red-first I/O test
    covers that path; 21-run Docker microbenchmarks measured 0.002329 ms per
    call at v5.88 versus 0.001290 ms currently (1.80x). A steady-state NYTProf run
    reduced PathRegistry chmod calls from 1,300 to 100 across 100 status
    requests. Problem 48's all-subroutine 50x objective remains open.
  - Problem 48 coverage follow-up: add deterministic tests for the explicit
    failed-stat error in state-directory hardening and the non-hash app-reference
    guard in Dancer request scoping. The focused Docker tests pass 157
    assertions; the combined library coverage report is 100.0% for statements,
    branches, conditions, and subroutines. A subsequent clean full Docker gate
    passed 272 files / 23,603 tests with 100.0% coverage across all four metrics
    (47,773 detail rows, no stale uncoverables); post-gate `perlsec`, syntax,
    POD, and diff checks passed. The performance objective remains open.
  - Problem 48: scope canonical path-identity results to one direct Dancer
    request. A red-first test showed repeated identical paths were resolved
    twice per request and verified the cache is discarded before the next.
    Across 100 byte-identical `/system/status` requests, exact PathIdentity
    calls fell from 1,300 to 600; 11 alternating Docker pairs measured 0.478
    ms/request without the request scope versus 0.463 ms with it (1.03x). This
    small route improvement does not meet the 50x-per-subroutine goal.
  - Problem 48: normalize each Dancer request from one request object, one
    PSGI environment read, and one read of each required header. A red-first
    Docker test reproduced 13 request acquisitions, four environment reads,
    and a duplicate Host read; the optimized path preserves exact normalized
    payloads and reduced a paired 200,000-call microbenchmark from 5.568 to
    3.665 microseconds per call (1.52x). This scoped result does not meet the
    50x-per-subroutine objective.
  - Problem 48 follow-up: map the seven known HTTP headers directly from their
    standard PSGI environment keys after comparing them against Plack's header
    normalization. A red-first Docker regression verifies exact values,
    rejects invalid environments visibly, and confirms the adapter does not
    invoke seven header accessors per request. Five alternating Docker pairs
    (200,000 calls per arm) preserved canonical JSON payload parity and
    measured 5.167 versus 2.578 microseconds per call (2.00x). This helper
    result remains below the 50x-per-subroutine objective.
  - Problem 48: precompute priority, collector, collector-order, and name sort
    keys for indicator lists of 32 or more records, avoiding method dispatch
    and repeated hash lookups inside Perl's sort comparator. A red-first test
    reproduced 191 comparator method calls and now verifies reference-order
    parity for an identical input list plus all-record retention. Alternating
    Docker benchmarks preserve exact canonical output and measured full-list
    gains of 1.04x at 50, 1.14x at 1,000, and 1.06x at 5,000 records. The
    final isolated 5,000-record sort measured 1.51x faster with exact order
    parity. The overall 50x goal remains in progress.

5.89  2026-10-14

  - Problem 48: reuse PageRuntime's Template Toolkit service only within one
    web request and one ordered include-root set. Template service setup fell
    from one construction per render to one per root set in that request; a
    21-pair Docker microbenchmark of 500 exact-parity renders measured 0.1825 ms
    per fresh render versus 0.1248 ms with scoped reuse (1.46x). A later
    request creates a fresh service, so edits to included files are visible.
    Undefined, reference-valued, and empty include roots are filtered before
    Template Toolkit receives them, and an empty result uses the `.` fallback.
    The three focused suites pass 397 tests. The final Docker coverage gate
    passed 272 files / 23,952 tests with 100.0% statement, branch, condition,
    and subroutine coverage across lib/ (47,723 rows, no stale uncoverables).
    Optional environment/tool checks emitted explicit skips. Required web
    security suites passed 459 tests; `perlsec`, syntax/POD, release-metadata,
    and diff checks passed afterward.
    The full all-subroutine 50x goal remains open.
  - Problem 48: share runtime-layer candidate and installed-skill inventory
    scopes for the duration of each Web::App request; nested all_paths reads
    reuse the active request scope. A Docker route regression bounds ancestor
    discovery to two scans despite the nested permission check and verifies
    configuration and runtime freshness across requests.

  - Problem 48: cache web settings only for the duration of one request so
    authorization and page chrome share a single settings read. A red-first
    Docker assertion observed three reads; the request now performs one, and
    merged config reads on the tested page/status route fall from seven to one.
    The exact merged snapshot and skill entries are passed to collector
    assembly; `web_settings` also derives SSL validity from that snapshot.
    The saved-page response remains 200, subsequent requests see saved config
    edits, and focused web/config tests pass. The rerun of the full Docker gate
    passed 271 files / 23,901 tests; statement, branch, condition, and
    subroutine coverage are 100.0% across lib/ (47,644 detail rows, no stale
    uncoverable annotations). Optional audit/browser/platform checks emitted
    explicit environment skips.

  - Problem 48: reuse the skill config entries already loaded while building
    merged configuration when assembling collectors, instead of rereading each
    skill config file in a second pass. A red-first test failed at two entry
    enumerations and now requires one. A 21-pair Docker benchmark over 250
    skills preserved canonical collector output and measured 27.324 ms for the
    prior double-read path versus 15.446 ms after (1.77x).

  - Problem 48: remove PathRegistry's eager Fcntl import from runtime metadata
    file-type checks and use Perl's cached lstat file tests instead. A red-first
    Docker import probe failed before the change; metadata symlink, directory,
    owner, and I/O regressions pass afterward. An alternating 21-pair Docker
    process benchmark measured 13.888 ms without Fcntl versus 15.017 ms with
    the former import (1.08x for isolated module startup). This is a narrow
    startup gain, not the 50x all-routine target.

  - Problem 48: reuse lazily constructed JSON::XS encoder and UTF-8 decoder
    instances in the shared JSON wrapper. Red-first Docker probes verify a
    single instance, output parity, and decoder recovery after malformed state
    input. Alternating 21-pair Docker benchmarks of 10,000 calls each measured
    1.41x encoding and 1.17x decoding with exact output parity; these narrow
    gains do not meet the 50x all-routine goal.
    The corrected full Docker gate passed 270 files / 23,465 tests with 100.0%
    statement, branch, condition, and subroutine coverage across lib/ (47,486
    detail rows, no stale uncoverable annotations).

  - Problem 48: avoid canonicalizing the selected home runtime path twice
    during each ancestor-layer walk when it also appears in the discovered
    home-layer list. A red-first Docker assertion caught three normalizations
    where two distinct uses are required; exact path spellings are now
    deduplicated before identity checks, while .d2 and canonical sibling paths
    remain separately normalized. The same NYTProf web-test workload retained
    3,373 calls and fell from 72.2 ms to 56.4 ms exclusive (1.28x).

  - Problem 48: after the home-root identity optimization, the canonical Docker
    gate passed 270 test files / 23,450 tests and reported 100.0% statement,
    branch, condition, and subroutine coverage across lib/ (47,461 detail rows)
    with no stale uncoverable annotations. Post-gate perldoc -t perlsec,
    syntax/POD, release-metadata, and diff checks passed; optional environment
    checks emitted explicit skips. The all-routine 50x target remains open.

  - Problem 48: replace the legacy bookmark parser's repeated linear scan of
    1,008 section keys with fixed-key hash lookup plus the existing CODE-number
    rule. A red-first Docker test caught 1,008 key fetches for CODE1000 and now
    requires none, while preserving acceptance through CODE1001. Exact-output
    21-run Docker microbenchmarks measured 122.13x for 10 CODE sections and
    127.47x for 1,001; the same NYTProf web-test workload fell from 482 ms to
    3.59 ms exclusive over 148 parser calls. This is scoped to parsing, not the
    50x every-subroutine objective.

  - Problem 48: the post-parser Docker coverage gate passed all 270 test files
    / 23,448 tests with 100.0% statement, branch, condition, and subroutine
    coverage across lib/ (47,455 detail rows) and no stale uncoverable
    annotations. Optional tooling/environment tests emitted explicit skips.
    Post-gate perldoc -t perlsec, Perl syntax/POD, metadata, and diff checks pass.

  - Problem 48: reuse the collector-sync indicator inventory when rendering
    the web status payload. A red-first Docker route regression caught two full
    indicator listings per status payload; it now performs one with identical
    response bytes. NYTProf showed 88 listings dropping to 46 over 44 status
    payload/summary operations and instrumented payload time falling 1.22x.
    This is route-specific, not the 50x all-subroutine target.

  - Problem 48: remove repeated runtime-root discovery from indicator listing.
    A red-first Docker test caught five root scans for four records; listing
    now resolves roots once and still reads current files in precedence order.
    Exact-output Docker benchmarks measured 8.06x for one layer and 12.15x for
    two layers. This is not the broader 50x target.

  - Problem 48: rerun the complete isolated Docker coverage gate after the
    status-payload inventory reuse. All 270 test files / 23,445 tests passed and
    statement, branch, condition, and subroutine coverage each measured 100.0%
    across lib/ (47,452 detail rows). Post-gate `perldoc -t perlsec`, changed
    module syntax checks, POD checks, and `git diff --check` passed. Optional
    environment/tool-dependent tests reported explicit skips. The 50x target
    and release gates remain open.

  - Problem 48: reuse ancestor runtime-layer discovery between
    runtime_layers and runtime_layer_root_for within one operation. A red-first
    Docker assertion caught two walks in all_paths; the operation now performs
    one while later operations still rescan for filesystem freshness. The
    focused PathRegistry suite passes 298 assertions.

  - Problem 48: complete Docker verification after the ancestor-walk and
    manual-sync change. After correcting README/POD parity, 269 files / 23,406
    tests passed with 100.0% statement, branch, condition, and subroutine
    coverage across lib/ (47,408 detail rows). Post-gate perlsec passed; the
    50x performance and release gates remain open.

  - Problem 48: test MCE using the actual Config::path_aliases path rather than
    only a generic JSON workload. Exact output parity passed. The 10,000-skill
    four-worker case reached 1.06x over nine runs, while an eight-worker run
    regressed; MCE is not adopted for production config loading. The 50x target
    remains open.

  - Problem 48: verify the application-level MCE benchmark tranche with the
    full Docker coverage gate: 269 files / 23,406 tests passed and all four
    library coverage dimensions reported 100.0%. Optional environment/tool
    checks were explicitly skipped; perlsec and diff checks passed. The 50x
    target and release gates remain open.

  - Problem 48: extend ordered MCE JSON config-read benchmarks to 10,000 and
    20,000 files. Exact serial/MCE output parity held. Warmed MCE was 1.27x at
    10k and 1.25x (4 workers) / 1.28x (8 workers) at 20k; first-map results
    varied from 0.98x to 1.63x. This is a 25-28% gain on a very large synthetic
    workload, not the 50x goal, and is not integrated into production loading.

  - Problem 48: scale the freshness-preserving resident path prototype to 250,
    1,000, and 5,000 skills, preserving exact response parity and mutation
    refresh at every size. Three alternating Docker runs measured 2.74x,
    1.49x, and 1.04x against fresh helper startup, respectively. The result
    shows context reuse alone cannot meet the target at large inventories;
    safely invalidated inventory/config caching is the next experiment, with
    dynamic Folder.pm providers still executed on each request.

  - Problem 48: use native getcwd when absolute PWD matches the physical
    process directory; validate distinct absolute PWD values by device/inode
    to preserve logical symlinks and use the physical path for stale values.
    A matched 21-pair Docker microbenchmark measured 1.7025 ms/call for the
    prior shell-backed Cwd::cwd and 0.0009 ms/call for the helper (1,891.6x)
    with equal output. The gain is specific to shell-backed cwd call sites,
    not a gain over native Cwd::getcwd. The 50x all-subroutine goal remains open.

  - Problem 48: rerun the corrected Docker coverage gate after restoring the
    one-native-getcwd common-path contract. All 270 test files / 23,458 tests
    passed; statement, branch, condition, and subroutine coverage were 100.0%
    across lib/ (47,464 detail rows, no stale uncoverables). Optional checks
    emitted explicit environment/tool skips; post-gate perlsec rendered.

  - Problem 48: rerun the full Docker coverage gate after the working-directory
    change and its empty-getcwd fallback case. It passed 268 files / 23,388
    tests with 100.0% statement, branch, condition, and subroutine coverage
    across lib/ (47,390 detail rows, no stale uncoverable annotations).
    Post-gate perldoc perlsec passed. Optional/environment-dependent tests
    reported explicit skips; the 50x performance target remains open.

  - Problem 48: benchmark Perl 5.40.1 built-in class/field/method syntax and
    subroutine signatures against conventional blessed-hash methods and
    ordinary subs. Seven alternating Docker runs preserved exact results, but
    built-in objects ran at 0.78x and signature subs at 0.72x relative speed;
    class/field/method also emitted experimental warnings. No slower or
    warning-producing implementation was adopted.

  - Problem 48: verify the resident path-context parity/mutation prototype in
    the full isolated Docker gate. The suite passed 268 files / 23,385 tests,
    with 100.0% statement, branch, condition, and subroutine coverage across
    lib/ (47,381 detail rows). The required post-gate perlsec check passed.
    Environment-dependent skips were reported explicitly. This validation
    does not establish the 50x all-subroutine objective; release gates remain
    open.

  - Problem 48: prototype a resident `paths -o json` context that reuses
    PathRegistry and Config instances but refreshes cwd, memoized state, aliases,
    skill inventory, and every Folder.pm result on each request. Exact parity
    with the fresh helper passed, including config replacement, dynamic
    Folder.pm environment values, and skill addition/removal. At 50 skills,
    nine Docker runs measured 29.336 ms fresh versus 6.719 ms resident (4.37x).
    This safe context-reuse design falls far short of 50x and remains research
    tooling, not a production server.

  - Problem 48: after adding the Inline::C/XS and request-scoped Memoize
    benchmark tooling, the full isolated Docker gate passed 267 files / 23,366
    tests. Statement, branch, condition, and subroutine coverage each measured
    100.0% across lib/ (47,381 detail rows). Optional/environment-dependent
    skips were reported by the suite. The post-gate perldoc perlsec check passed.

  - Problem 48: add a request-boundary Memoize comparison and regression test.
    The isolated Docker benchmark compared uncached path identity, an operation-
    local hash, and Memoize flushed at each operation, requiring exact result
    parity and fresh symlink targets between operations. For 1,000 unique
    paths repeated twice over 15 runs, medians were 7.705 ms uncached,
    4.246 ms with a local hash (1.81x), and 5.006 ms with Memoize (1.54x).
    Memoize is slower than the scoped hash and is not adopted in the application.

  - Problem 48: add a reproducible Docker benchmark comparing the Perl PBKDF2
    loop, the existing OpenSSL FFI::Platypus adapter, and an Inline::C-generated
    XS wrapper around the same OpenSSL function. A 9-assertion test checks CLI
    validation and native output parity. Five production-work-factor runs
    matched digests: Perl 287.413 ms, FFI 31.073 ms (9.25x), Inline::C/XS
    31.379 ms (9.16x); Inline compilation added 610.263 ms outside the routine
    median. Inline::C/XS adds no meaningful gain over FFI and remains research
    tooling only.
  - Problem 48: add an ordered-parity MCE scaling benchmark for 50, 250, 1,000,
    and 5,000 skill config files, including worker startup for each workload.
    In 15 paired Docker samples, four workers measured cold ratios of 0.21x,
    0.90x, 0.97x, and 0.89x versus serial; warmed ratios were 0.92x, 1.18x,
    1.13x, and 1.18x. MCE module import is excluded. This does not justify
    production use for the one-shot path command. The focused Docker test passed
    11 assertions with MCE available. The latest full Docker gate passed 265
    files / 23,316 tests with all four lib/ metrics at 100.0% (47,375 detail
    rows); nine optional files and three MCE-only assertions skipped in the
    standard image. Post-gate perlsec passed.
  - Problem 48: extend the PathRegistry freshness regression to cover skills
    added and removed between inventory scopes and disable markers created and
    removed between lookups. Reusing the same Config and PathRegistry objects
    also sees replaced skill config bytes and a newly installed skill; a
    reused Config follows a retargeted runtime symlink. Repeated calls also
    reevaluate dynamic Folder.pm behavior after an external sidecar-file change. Focused
    Docker tests passed 293 PathRegistry and 33 CLI test assertions; these
    verify current freshness behavior, not a resident cache or the 50x target.
  - Problem 48: refresh runtime-layer discovery on unscoped PathRegistry calls
    instead of retaining results for the object's lifetime. A red-first Docker
    regression reproduced stale project-layer state after create/remove; the
    298-assertion PathRegistry suite passes with reuse scoped to one operation.
    The full Docker suite passed 265 files / 23,392 tests. A fresh instrumented
    coverage-gate run passed 265 files / 23,330 tests and measured 100.0%
    statement, branch, condition, and subroutine coverage across lib/ (47,381
    detail rows). The required web-update, static-file, and SSL security tests
    passed; optional-tool skips are recorded by the suite. This fixes stale
    lookup state, not the broader 50x performance objective.
  - Problem 48: compare a fresh Devel::NYTProf profile of the same
    `paths -o json` helper against the v5.88 source in Docker. The output bytes
    matched; profiled handler time was 59.4 ms on v5.88 and 47.9 ms currently
    (1.24x). PathRegistry `_path_identity` exclusive total fell from 2.37 ms
    over 133 calls to 90 microseconds over 103 calls, about 20x per call.
    This new matched profile still fails the 50x requirement and covers only
    routines called by this one handler.
  - Problem 48: measure tiny native-call overhead in Docker. A Perl path
    accessor ran at 12.43 million calls/second versus 5.58 million for
    FFI-bound getenv (same returned string; about 2.23x slower through FFI).
    This is boundary-cost evidence, not a production code comparison.
  - Problem 48: avoid a duplicate top-level disabled-marker stat during
    include-disabled nested-skill inventory. A red-first Docker regression
    verifies the marker is checked once and that empty inputs remain safe.
    Exact output parity held in 51 paired runs: at 50 skills, 0.599 ms became
    0.496 ms (1.21x); at 250 skills, 3.089 ms became 2.585 ms (1.19x).
    This narrow inventory gain does not meet the 50x-per-subroutine target.
    The full Docker gate passed 264 files / 23,296 tests, with 100.0% across
    all four lib/ coverage metrics (47,375 detail rows); nine optional or
    environment-dependent tests reported explicit skips. Post-gate perlsec
    completed successfully.
  - Problem 48: reuse canonical identities for identical filesystem path
    spellings only during one `PathRegistry::all_paths` aggregate. The local
    cache is discarded at return so later lookups resolve the live filesystem;
    a red-first Docker regression covers scoped hits, undefined paths, and
    post-scope freshness. Exact JSON parity held in 51 paired runs; the
    sub-millisecond aggregate measured 0.576 ms uncached and 0.424 ms cached
    (1.36x), not the 50x-per-subroutine target. The refreshed full Docker gate
    passed 264 files / 23,292 tests with 100.0% coverage for all four `lib/`
    metrics (47,360 detail rows); post-gate `perlsec` also passed.

  - Problem 48: share the top-level skill directory listing between nested
    discovery and layered path-config reads during one `dashboard paths`
    command. The request-scoped snapshot retains no cross-command state and
    does not cache disabled markers. Red-first Docker tests verify one scan and
    visibility of skills created by the next command. A two-layer 50-skill
    parity benchmark measured 1.465 ms versus 1.136 ms (about 1.29x); this
    targeted gain does not meet the 50x-per-subroutine objective.
  - Problem 48: cover the remaining native-auth digest and empty-HOME path
    builder branches directly. The Docker full suite passes 264 files / 23,292
    tests and reports 100.0% statement, branch, condition, and subroutine
    coverage across lib/ (47,360 detail rows, no stale uncoverable annotations).
    This closes the coverage gate for this worktree, not the 50x performance
    objective.
  - Problem 48: make the PBKDF2 native-adapter test cover FFI setup and call
    branches even when optional FFI packages and libcrypto are absent. A fake
    FFI boundary now exercises digest parity, cached/lazy initialization,
    incompatible byte inputs, buffer bounds, missing-library behavior, and
    explicit native errors. Docker's isolated Devel::Cover run reports 100.0%
    statement, branch, condition, and subroutine coverage for the adapter; this
    is module-level test evidence, not completion of the global 50x objective.
  - Problem 48: defer terminal table-rendering imports on the JSON-only paths
    route. Docker regression tests verify JSON output avoids TableHelpers while
    table output still loads it. The startup reduction is isolated and does
    not satisfy the 50x-per-subroutine objective.
  - Problem 48: share runtime-layer candidate discovery within one aggregate
    `dashboard paths` operation, reducing repeated ancestor walks while keeping
    the candidate list request-scoped. Docker tests verify bounded scan count
    and confirm a later lookup sees a layer created after the inventory call.
    A 15-lookup benchmark measured 1.162 ms without the scope versus 0.305 ms
    with it (about 3.81x); this scoped gain does not meet the 50x objective.
  - Problem 48: share one nested-skill inventory between configured path
    aliases and `Folder.pm` alias discovery during `dashboard paths`, retaining
    disabled-skill filtering for executable Folder providers. Red-first Docker
    tests caught the prior two scans and check diagnostics, disabled-state
    propagation, and unchanged JSON output. A 50-skill/two-depth benchmark
    preserved exact canonical JSON and measured 4.355 ms separate versus
    3.317 ms shared (about 1.31x); this scoped result does not meet the 50x
    target.
  - Problem 48: reuse one installed-skill config inventory while `paths`
    combines global config with skill-qualified path aliases. A red-first
    Docker regression caught two `installed_skill_entries` scans; the
    operation now performs one and validates shared-entry shapes and options
    directly. A synthetic 50-skill/two-layer benchmark preserved canonical
    JSON and measured 4.304 ms before versus 2.342 ms after (about 1.84x).
    This routine-level result does not meet the 50x goal.
  - Problem 48: normalize a candidate path once while `runtime_layer_root_for`
    checks multiple runtime roots, retaining canonicalization of every root
    and component-boundary matching. Red-first Docker tests caught three
    candidate resolutions where one is now required and cover exact, parent,
    sibling, and malformed identity comparisons. A 100-pair five-root
    benchmark preserved the selected-root result and measured 0.059 ms before
    versus 0.042 ms after (about 1.41x); this routine-level gain is not 50x.
  - Problem 48: merge only the `path_aliases` domain from layered skill config
    when building path aliases, instead of recursively merging unrelated
    fields and then merging the full skill namespace into the global view.
    Red-first Docker coverage verifies exact alias output and that unrelated
    fields/fragments are not merged. With 50 two-layer skills and 80 unrelated
    config fields, 30 alternating pairs measured 22.262 ms before versus
    9.774 ms after (about 2.28x). This scoped result remains below 50x.
  - Problem 48: add regression assertions for malformed shared skill-config
    inventory and nested-skill invalid-name, disabled-state, and write-location
    branches. The latest isolated Docker coverage gate passes all 263 test
    files and 23,604 tests with 100.0% statement, branch, condition, and
    subroutine coverage across lib/ (47,304 detail rows); the overall 50x
    performance objective remains open.
  - Problem 48: carry validated nested-skill ancestry forward during alias
    discovery, avoiding repeated prefix validation and queue shifting. A
    red-first Docker test failed on repeated validation before the change;
    inventory write locations match the strict resolver for valid, git-managed,
    and invalid-name cases. A 50-skill/two-depth benchmark preserved exact
    canonical JSON and improved this routine from 5.407 ms to 2.048 ms median
    (about 2.64x). Checkout NYTProf showed nested discovery falling from
    12.1/3.60 ms inclusive/exclusive to 6.89/1.06 ms. This is a scoped routine
    improvement, not the requested 50x result.
  - Problem 48: pass each nested skill's already-discovered directory chain
    and safe config write location into skill-alias merging, removing a
    repeated layer/path resolution per skill. Red-first Docker tests verify
    write-location parity and fail if Config resolves the chain a second time.
    A 50-skill/two-layer/two-depth synthetic benchmark preserved exact output
    and measured 4.216 ms before versus 2.347 ms after (about 1.80x); this is
    one routine, not the 50x whole-dashboard target.
  - Problem 48: batch installed-skill roots and their runtime layers before
    loading layered collector/API-key config, instead of rescanning all skill
    roots once per skill. Config preserves home-to-project merge order and
    disabled layer behavior. Red-first Docker tests cover grouped inventory
    and Config's no-rescan contract. A 50-skill/four-layer synthetic benchmark
    verified identical serialized results and measured 4.431 ms legacy versus
    3.409 ms grouped lookup (about 1.30x); it does not meet the 50x target.
  - Problem 48: parse canonical generated-prompt options locally in the `ps1`
    helper and load Getopt::Long only when unsupported or abbreviated syntax
    needs compatibility parsing. Red-first Docker checks cover values, output,
    warnings, and the abbreviation fallback. A 40-pair cold-process control
    measured 29.338 ms without Getopt::Long initialization versus 33.174 ms
    with it preloaded (about 1.13x). A 30-pair public-command comparison
    measured 65.535 ms versus 72.307 ms (about 1.10x), confirming the import
    saving survives switchboard dispatch; this remains far below the 50x goal.
  - Problem 48: use the repository library path supplied by the public
    switchboard in the `ps1` helper and retain FindBin for standalone fallback.
    A red-first Docker regression verifies both routes and output parity except
    for the live timestamp; no warnings occur. A 25-pair cold-helper control
    measured 33.364 ms versus 33.671 ms (about 1.01x), not a 50x result.
  - Problem 48: make the prompt helper use the repository library path supplied
    by the switchboard and defer FindBin to standalone fallback execution. A
    red-first Docker regression caught the eager import; both dispatch paths
    now preserve prompt output (apart from its live timestamp) without warnings.
    An alternating 25-pair cold-helper benchmark measured 33.364 ms on the
    supplied-path route versus 33.671 ms on fallback (about 1.01x), so this is
    only a small dispatch-path saving and not evidence for the 50x target.
  - Problem 48: use the repository library path that the public switchboard
    already passes to the `paths` helper, avoiding FindBin initialization on
    that dispatch while retaining FindBin for standalone fallback runs. A
    red-first Docker subprocess test caught the eager import; regression tests
    verify both paths, no warnings, and byte-identical JSON. An alternating
    100-pair fresh-process helper benchmark measured 26.963 ms with fallback
    versus 24.782 ms with the supplied library path (about 1.09x); this is not
    an end-to-end command or 50x claim.
  - Problem 48: update the explicit coverage QA fixture for the path-bound
    `Config->for_path_config` constructor. Its old `Config->new`-only stub
    failed under the complete suite; the corrected Docker gate passed after
    that change. The final gate, including the paths helper's lazy FindBin
    change, passed 263 files / 23,551 tests and reported 100.0% statement,
    branch, condition, and subroutine coverage (46,954 detail rows, no stale
    uncoverable annotations). `perldoc perlsec` was run in the same Docker
    bench after the final gate. The performance objective remains open.
  - Problem 48: avoid constructing a FileRegistry for prompt collector reads.
    A red-first Docker regression showed ordinary ps1 loaded FileRegistry even
    though collector configuration only needs PathRegistry. Config now offers
    a path-only collector view, and ps1 uses it; the prompt-loader and config
    suites pass 327 tests. An alternating 25-cycle fresh-process benchmark
    measured 76.918 ms versus 85.200 ms with FileRegistry force-loaded (about
    1.11x). The full Docker coverage gate passed 263 files / 23,541 tests with
    all four library metrics at 100.0% (46,955 detail rows). This prompt-
    specific gain does not meet the 50x target.
  - Problem 48: use a path-bound Config view for prompt and path CLI config
    operations, avoiding FileRegistry loading and construction where Config
    only needs PathRegistry. A red-first Docker test blocked FileRegistry and
    failed on `dashboard paths -o json`; prompt and path CLI suites then passed
    361 tests. An alternating 25-cycle fresh-process benchmark measured
    `dashboard paths -o json` at 64.636 ms with FileRegistry unloaded versus
    72.077 ms with the module force-loaded (about 1.12x). The control measures
    module-load cost, not the complete pre-change implementation; no 50x claim
    is made.
  - Problem 48: defer Capture::Tiny loading from the prompt helper and
    IndicatorStore's prompt-only refresh path until a tmux ticket lookup or
    full project/Git core-indicator refresh actually needs subprocess capture.
    A red-first Docker test used a failing module stub to prove ordinary
    `ps1 --no-indicators` does not load Capture::Tiny; prompt-loader, CLI smoke,
    and IndicatorStore suites passed 1,322 tests. An alternating 25-cycle
    cold-process Docker benchmark measured 71.498 ms with lazy loading versus
    78.034 ms with Capture::Tiny force-loaded (about 1.09x). This remains a
    prompt-specific gain, not the 50x all-subroutine target.
  - Problem 48: make optional-FFI test fixtures skip only their native-specific
    checks when FFI modules are absent, while preserving fallback checks and
    exercising the full native path when the test modules are installed. The
    final Docker coverage gate passed 263 files / 23,539 tests with all four
    library coverage metrics at 100.0% (46,943 detail rows).
  - Problem 48: use a small fast parser for the canonical `-o` and `--output`
    forms in the path CLI instead of initializing Getopt::Long for routine
    output selection. Unrecognized syntax still falls back to Getopt::Long;
    red-first Docker tests verify both paths and byte-for-byte path output
    parity. An in-process Docker microbenchmark measured 130.4 ms per 100,000
    fast parses versus 1,604.1 ms with Getopt::Long (about 12.3x for this
    parser workload). A paired 20-cycle full-command benchmark returned
    identical 981-byte JSON and measured 67.578 ms versus 72.113 ms through the
    compatibility parser (about 1.07x). No 50x whole-command claim is made.
  - Problem 48: defer Getopt::Long loading until path verbs parse options.
    The path project-root verb now avoids the option-parser initializers seen
    in its checkout NYTProf trace. A red-first Docker probe caught the eager
    import, and the focused lazy-load/path suites pass 57 tests. An alternating
    25-run process benchmark measured 28.863 ms without loading Getopt::Long
    versus 32.937 ms with it preloaded (about 1.14x). A cumulative 20-cycle
    ABBA comparison of the same project-root handler, including the former
    unused Config/FileRegistry construction, measured 26.852 ms current versus
    35.489 ms pre-change with exact output parity (about 1.32x). The 50x
    overall target remains unmet.
  - Problem 48: defer Config and FileRegistry loading for the path
    project-root and locate verbs, which do not read or change configured
    aliases. A red-first Docker probe caught both modules loaded before and
    during project-root dispatch; the new lazy path passes alongside the
    31-test paths suite. An alternating 25-run process benchmark measured
    30.372 ms with lazy imports versus 36.041 ms with both modules eagerly
    loaded (about 1.19x). This command-specific gain does not approach the
    50x all-subroutine target.
  - Problem 48: remove the unused Config import from the command switchboard
    and load IO::Handle, IO::Select, IPC::Open3, and Symbol only when executable
    command hooks need them. A red-first Docker regression caught all five
    eager imports; the 1,029-assertion hook-heavy CLI smoke suite passes. A
    paired 25-run Docker benchmark measured 31.520 ms with lazy imports versus
    33.104 ms with the modules preloaded (about 1.05x); this is a small startup
    gain, not the 50x goal.
  - Problem 48: reuse the canonical identity of a runtime-layer ancestry stop
    directory and stop canonicalizing when the directory walk reaches that exact
    path string. A red-first Docker regression reduced stop-path identity
    lookups from three to one in a three-level project-root fixture; the
    exact-root path still skips canonicalization. No timing speedup is
    claimed from that call-count result alone.
  - Skip redundant filesystem canonicalization when a path ancestry check
    receives the exact same nonempty path string for both operands. A red-first
    Docker test proves this performs zero `abs_path` calls while preserving the
    same boolean result; differently-spelled paths still canonicalize.
  - Problem 46 follow-up: pass `LC_CTYPE=C.UTF-8` to the Docker workspace tmux
    handoff so prompt/status icons survive POSIX locale without changing `LANG`.
    A Docker tmux 3.6 locale matrix confirms a UTF-8 client also fixes display
    when attaching to an already-running server that started under the C locale.
    Keep the override scoped to the Docker Compose handoff: ordinary workspace
    and compatibility ticket environments do not force the user's locale.
    Docker tests cover both environment contracts and the real tmux handoff.
  - Problem 48: accelerate helper-password PBKDF2-HMAC-SHA256 through
    OpenSSL FFI when a compatible libcrypto is available, preserving the
    existing salt, iteration count, stored digest format, and Perl fallback.
    Keep FFI::Platypus and FFI::CheckLib optional runtime recommendations;
    absent FFI modules now select the documented Perl fallback rather than
    breaking installs or authentication.
  - Reduce lightweight CLI startup by loading POD usage rendering only on
    usage-error paths; Docker NYTProf confirms it is absent from `version`.
  - Defer Platform loading from the switchboard, path registry, and helper-name
    lookup until platform behavior is needed; expose version metadata through a
    lightweight module instead of loading the application graph.
  - Docker NYTProf confirms `version` no longer loads Platform, InternalCLI,
    Handle, or FileRegistry; 15-run median improved to 35.114 ms (~1.62x versus
    the recorded 5.88 baseline), still far short of the 50x all-routines goal.
  - Replace measured hot-path `Cwd::cwd` subprocess calls with a shared,
    device/inode-validated logical PWD lookup; retain Cwd fallback for stale
    or absent PWD and preserve valid symlink paths. The 15-run `version`
    median fell to 32.287 ms, about 1.76x versus the 5.88 baseline.
  - Add digest-compatibility, unsupported-input, missing-library, native-error,
    and authentication fallback tests; document why MCE is not safe for the
    current sequential collector startup contract.
  - Reuse one state-root resolution while building the aggregate path inventory,
    avoiding duplicate runtime metadata writes for collector and indicator roots.
  - Keep PathRegistry's passwd-name fallback on the guarded Perl built-in so a
    lightweight path command does not load the full Platform helper module.
  - Reuse the resolved state-root account name while its environment inputs
    are unchanged; preserve detection of changed environment overrides.
  - Wire the shared indicator store into the standalone PSGI prompt; a
    red-first Docker test now loads app.psgi and requests /system/status.
  - In an isolated no-username environment, reduce passwd fallback calls from
    80 to 1 over 20 requests; instrumented edge-route time improves 1.2x.
  - Avoid rewriting verified runtime state metadata on repeated root lookups;
    repair stale or loose files and refuse symlinked metadata. The actual
    app.psgi status route improves 4.25x under NYTProf.
  - Docker 15-run medians: version 31.486 ms, paths 71.309 ms, and ps1 83.641 ms;
    the requested 50x all-routines target remains unmet.
  - Keep temporary test-staged source trees out of Devel::Cover instrumentation
    without reducing the enforced lib/ coverage scope. The previous Docker gate
    passed 262 files / 23,440 tests at 100.0% across all four metrics; follow-up
    isolated generated eval source, with the final full rerun pending.
  - Separate dynamically generated saved-Ajax and CODE sandpit source from
    PageRuntime.pm's static source map, and make the coverage checker reject
    Devel::Cover warning diagnostics instead of accepting a 100.0% total.
    The final Docker gate passed 262 files / 23,442 tests and reports 100.0%
    statement, branch, condition, and subroutine coverage with no warnings.
  - Close the final PathRegistry short-circuit coverage gaps. The fresh Docker
    run passes 263 files / 23,488 tests with all four `lib/` coverage metrics
    at 100.0% (46,834 detail rows; no stale uncoverable annotations).
  - Full Docker coverage gate before the optional-FFI follow-up: 263 files /
    23,498 tests passed, and statement, branch, condition, and subroutine
    coverage each remained at 100.0% across `lib/` (46,844 detail rows; no
    stale uncoverable annotations). Seven environment/tool-dependent test files
    reported explicit skips; the 50x performance target remains open.
  - Make the FFI accelerator an optional runtime recommendation and handle
    missing FFI module files using the tested Perl PBKDF2 path. Do not import
    optional FFI modules at adapter compile time: a fresh interpreter without
    FFI now loads the module and selects the portable fallback. The red-first
    Docker test reproduces the previous load failure; the focused adapter has
    100.0% statement, branch, condition, and subroutine coverage. The final
    Docker gate passed 263 files / 23,518 tests with 100.0% on all four `lib/`
    metrics (46,868 detail rows); seven environment/tool-dependent files
    skipped with explicit reasons. The overall 50x target remains open.
  - Remove the unused and prohibited HTTP::Tiny runtime prerequisite from
    Dist::Zilla, Makefile.PL, and cpanfile; guard its absence in release tests.

5.88  2026-10-12

  - Problem 46 follow-up: pass `--no-build` when starting a stopped Docker
    workspace by default, preventing Compose from implicitly building a missing
    image; only `-d -C` opts into a cached build.
  - Reproduce the tmux socket failure through the installed outer `d2` and
    verify the checkout command passes TMUX_TMPDIR=/dev/shm and attaches to
    the container tmux session.

5.87  2026-10-12

  - Problem 46 follow-up: pass TMUX_TMPDIR=/dev/shm into the container-side
    workspace command so even an older installed d2 uses the container's
    writable tmux socket directory; preserve the host alias and live TTY.
  - Add a regression assertion and record the reproduced outer-handoff failure
    and successful Docker tmux attach.

5.86  2026-10-11

  - Problem 46: make Docker workspace startup build-free by default; lowercase
    `-c -d` starts a stopped workspace with `up -d workspace`.
  - Add uppercase `-C` as an explicit cached-build opt-in and stream Compose
    build progress directly to the terminal; never rebuild a running service.
  - Keep Docker tmux attached through a real TTY and select a writable socket
    directory when the container's `/tmp` bind mount is not writable.
  - Add parser, help, completion, orchestration, build-output, and tmux
    regressions and update the Problem 46 operator documentation.

5.85  2026-10-11

  - Problem 46: make the Docker workspace handoff allocate a TTY and pass the
    terminal streams through directly so tmux can attach interactively.
  - Let workspace and ticket tmux commands honor TMUX_TMPDIR and select
    writable /dev/shm when a container's /tmp bind mount is not writable, so
    later commands reconnect to the same tmux server.
  - Add container-backed regressions and document the reproduced permission
    failure, root cause, terminal behavior, and verification flow.

5.84  2026-10-10

  - Problem 47: fix the Docker Hub publication timeout by avoiding a second
    full test-suite run under QEMU for each target architecture. Full suite and
    coverage gates remain mandatory before publication; install the checked-out
    tarball with cpanm --notest in the runtime image.
  - Add a red-first workflow contract test for this install policy and document
    the distinction between release verification and image assembly.

5.83  2026-10-10

  - Problem 46 regression: reuse an already-running Docker workspace service
    without rebuilding the container or risking in-container progress.
  - When the service is stopped, start it with cached `up -d --build` and never
    disable Docker's build cache; abort visibly if its running state is unknown.
  - Add Docker regression tests for running, stopped, cache, and failed-probe
    paths; document the workspace lifecycle and verification procedure.

5.82  2026-10-10

  - Problem 46 regression: isolate the in-container workspace path alias in an
    owner-only `/dev/shm` config overlay so bind-mounted host aliases are not
    overwritten.
  - Reject symlinked config overlays and test that the host path remains
    unchanged while the container-side alias resolves to /workspace.
  - Problem 47 Scorecard follow-up: pin the multi-platform Ubuntu base image by
    digest so the Docker Hub build has immutable container inputs.
  - Add a regression assertion for the base-image digest and document the
    immutable image dependency contract.

5.81  2026-10-10

  - Problem 47: add a GitHub Actions Docker Hub build for current master source.
  - Run install.sh, build a fresh Dist::Zilla archive, and install/test that
    exact archive so the image does not ship an older MetaCPAN copy.
  - Publish versioned and latest tags for linux/amd64 and linux/arm64 using
    pinned workflow actions and configured Docker Hub credentials.
  - Document the master-source build and Apple Silicon/Linux arm64 behavior.

5.80  2026-10-10

  - Reissue Problem 46 verification with final 5.79 blank-container install
    and image-version evidence, and record the outstanding repository-level
    Scorecard gates separately from the implementation results.

5.79  2026-10-10

  - Reissue Problem 46's distribution with the completed problem report.
    Runtime behavior is unchanged from 5.78; this version is required because
    the 5.78 image build has already been recorded today.
  - Preserve the ordered Docker workspace startup, alias registration, and
    in-container workspace behavior with both help and completion forms.

5.78  2026-10-10

  - Problem 46: add `d2 workspace -c <alias> --docker` and the `-d` shorthand
    for running a normal tmux workspace inside a configured Compose service.
  - Start the `workspace` service first, add the alias at `/workspace` in the
    container, then start the inner workspace; stop with an actionable error
    before subsequent commands if any earlier stage fails.
  - Preserve the existing local tmux flow without Docker mode; document and
    complete help and shell option completion for both Docker spellings.

5.77  2026-10-09

  - Finalize Problem 45's report and package evidence after the 5.76 runtime
    image and blank-container installation/integration checks passed.
  - Keep the source README/POD, problem reports, changelog, and release version
    aligned in the final distribution archive.

5.76  2026-10-09

  - Problem 45: add Docker-style -t timestamps and -f follow mode to dashboard
    logs, including combined web/collector output, all collectors, and a named
    collector; keep --tail N, --tail=N, and -n N composable across the views.
  - Keep collector record timestamps when available; timestamp web log lines at
    read time because the persisted web log does not store per-line event time.
  - Route `dashboard serve logs` through the shared parser and update help,
    shell-completion metadata, README/POD, and integration documentation.
  - Add follow, timestamp, mixed-source, and coverage-closure regressions.

5.75  2026-10-09

  - Finalize Problem 45's 5.75 package and runtime image after the 5.74 image
    version was consumed; keep the Docker-style log tail implementation and
    its completed verification report aligned in the final artifacts.

5.74  2026-10-09

  - Problem 45: add Docker-style --tail N and --tail=N limits to dashboard logs.
  - Apply the line limit to combined web/collector output and scoped log views;
    retain -n as a compatible alias and reject malformed or negative counts.
  - Cover the public CLI, zero-line handling, invalid input, and command help
    with Docker-executed regressions; document the behavior in the manual.

5.73  2026-10-08

  - Audit and repair the numbered problem report: entries 1–44 now appear in
    order, and Problem 39 is explicitly recorded as an unassigned numbering gap.
  - Label the October 3 status paragraph in the historical report as a
    superseded snapshot so it cannot be mistaken for current delivery status.
  - Add release-metadata regression checks for complete, unique, ordered
    problem-index entries and historical evidence coverage.
  - Exclude generated terminal transcript logs from source distributions so
    operator-local output cannot leak into a release archive.

5.71  2026-10-08

  - Complete Problem 43: resolve Docker Compose skill env using only the
    explicitly selected effective service(s), preventing another skill's
    same-named value from overwriting interpolation during materialization.
  - Preserve deferred local-base inference and the existing all-base-service
    behavior when no selected service matches or the operation selects none.
  - Add red-first sequential foo/bar Compose environment regressions and update
    the Docker Compose and release verification documentation.

5.70  2026-10-08

  - Finalize Problem 44's release evidence in a distinct package version after
    the image-build guard rejected reusing the already-built 5.69 version.
  - Rebuild the documented tarball and runtime image as 5.70; repeat the
    blank-environment installation and integration gates against that exact
    final artifact.

5.69  2026-10-08

  - Complete Problem 44: caller-exported environment values take precedence
    over home, skill, and project .env/.env.pl files while unset keys retain
    the documented layer precedence.
  - Preserve caller environment values through both the public dashboard
    switchboard and the direct nested-skill environment loader; keep env-audit
    provenance accurate and restore caller values when a file fails to load.
  - Pass all 258 Docker test files / 22,690 tests with 100.0% statement,
    branch, condition, and subroutine coverage; pass the 459-test web/security
    trio, 7/7 CPANTS kwalitee indicators, and all 348 POD syntax checks.
  - Build the 5.69 tarball and image, verify the image version, and install
    and test the tarball with cpanm (without --notest) in a blank container;
    the installed-runtime integration flow passes.

5.68  2026-10-08

  - Complete Problem 42: load home runtime environment files before skill
    environment files so skill values override matching home defaults; retain
    deeper project runtime precedence.
  - Add red-first container regressions for home-versus-skill precedence and
    preservation of project runtime overrides.

5.67  2026-10-08

  - Complete Problem 41: Docker is no longer a built-in indicator; stale core
    Docker records are removed safely while configured collector/custom records
    are retained.
  - Record full Docker test/coverage, security, release archive, blank-container
    installation, kwalitee, and runtime-image verification for version 5.67.

5.66  2026-10-07

  - Problem 41: remove Docker availability probing and the Docker status record
    from built-in indicator refresh; Docker remains available as an explicitly
    configured collector.
  - Remove stale persisted records matching the retired built-in Docker
    signature without deleting collector-owned or customized docker indicators;
    serialize cleanup with status writers to protect concurrent collector updates.
  - Add Docker-container regressions for absence of the built-in, safe legacy
    record cleanup, collector ownership preservation, and prompt-only refresh.

5.65  2026-10-07

  - Complete Problem 40 for explicit-file-only projects: detect leading -f and
    --file inputs as the base when no conventional compose file exists, then
    defer isolated service and service-environment discovery until the base
    Compose config returns its service map.
  - Add a red-first Docker regression for explicit-file-only base resolution,
    CLI-only service names, selected overlays, and delayed skill environment.
  - Re-run all 258 Docker test files (22,609 tests) at 100.0% statement,
    branch, condition, and subroutine coverage before the 5.65 release build.
  - Build the 5.65 tarball and image, verify the image runtime version, and
    pass 100% CPANTS kwalitee on the archive.
  - Install the tarball with cpanm without --notest in a blank Docker image and
    pass its complete installed-runtime integration script.

5.64  2026-10-07

  - Fix Problem 40: resolve the base Compose config before selecting isolated
    service overlays, so only services present in Compose's effective base
    configuration can activate home, project, or nested-skill overlays.
  - Preserve explicit Compose global options, -f inputs, and project-directory
    settings without slicing the generated argv by file count.
  - Reject Compose service names that are empty, directory-navigation names,
    or contain path separators before runtime service lookup.
  - Add Docker regressions for config-first selection, marker behavior, nested
    skill overlays, CLI-only service names, explicit-file-only bases, native
    help passthrough, and parser and materialization error paths.
  - Verify the complete Docker suite (258 files, 22,615 tests) at 100.0%
    statement, branch, condition, and subroutine coverage; the focused web
    security tests also pass (459 tests).

5.63  2026-10-06

  - Complete Problem 38's config-first Compose release with README, module,
    distribution, and package versions aligned after the image guard rejected
    reusing version 5.62.

5.62  2026-10-06

  - Complete Problem 38 verification records: every Compose operation builds
    the effective base config first, then runs against the materialized file.
  - Record the successful Docker coverage, release-image, and blank-container
    install/integration gates without reusing version 5.61.

5.61  2026-10-06

  - Fix Problem 38's source-side UTF-8 gap: parse a normalized raw-byte copy
    of each local Compose base before service discovery, without rewriting it.
  - Add a Docker regression that carries a legacy pound byte from the source
    base through config materialization and into the operational Compose file.

5.60  2026-10-06

  - Rebuild the final Problem 38 package with the completed verification report
    included; keep all module and distribution versions aligned.

5.59  2026-10-06

  - Keep the D2 image's active /root/perl5 library aligned with the release
    tarball by installing the archive into that runtime-first local library.
  - Add regression coverage for the local image recipe's explicit local-lib
    target so a fresh image runs the version it just built.

5.58  2026-10-06

  - Problem 38 follow-up: materialize the effective base Compose config before
    every Docker Compose operation, including when no explicit layered files
    were resolved; execute the requested verb using that merged file.
  - Add regression coverage proving a fileless `ps` request first invokes
    `docker compose config` and then uses the generated config file.

5.57  2026-10-06

  - Problem 38: normalize captured merged Docker Compose YAML to valid UTF-8,
    preserving valid UTF-8 and converting isolated Windows-1252 bytes before
    the temporary file is consumed by config, up, down, build, ps, logs, or
    other Compose operations.
  - Add Docker regression coverage for the malformed-byte producer case and
    verify the normalized merged YAML parses while preserving the pound sign.
  - Make the nav I/O-failure test assert that its blocked layer contributes no
    entries while allowing other active runtime layers to contribute nav.

5.56  2026-10-06

  - Problem 37: use a cron collector's scheduler heartbeat for watchdog stall
    detection instead of treating the quiet time between scheduled executions
    as repeated collector failures.
  - Add a Docker regression proving a live cron loop awaiting its 7–8am run
    does not raise the watchdog's repeated-restart warning at 10pm.
  - Problem 34 follow-up: route public Docker Compose operations through the
    materializing runner instead of directly executing the unresolved file list.
  - Keep the merged file alive until Compose exits and preserve streamed output
    for build, up, down, and other operational commands.

5.54  2026-10-05

  - Problem 34 follow-up: keep the invocation Compose project directory when
    layered files are materialized for build, up, down, and other operations.
  - Preserve an explicit user-supplied --project-directory, reject malformed
    Compose arguments before execution, and cover up, build, and down in Docker.

5.53  2026-10-03

  - Implement Problem 25's external skill-help passthrough and Problem 36's
    layered skill-command completion, with regression coverage for both.
  - Rebuild the project Docker image from a flattened local base after the
    previous base image exceeded the overlay mount-option limit.
  - Pass the complete D2 test suite (258 files / 22,403 tests) and the 5.53
    blank-environment integration run with tests enabled.
  - Keep the internal/external CLI help boundary and layered skill completion
    regressions covered in the test suite.

5.52  2026-10-03

  - Problem 25 follow-up: keep help handling within internal CLI dispatch and
    pass native help flags through to dotted external skill commands.
  - Problem 36: merge dotted skill completion names across project and home
    runtime layers while de-duplicating shared commands.
  - Add Docker regression coverage for external CLI help passthrough and
    layered project/home skill command completion.

5.51  2026-10-03

  - Problem 25 follow-up: keep help handling within internal CLI dispatch and
    pass native help flags through to dotted external skill commands.
  - Add a Docker regression fixture for `d2 tira.tasklist.prune --help` and
    document the internal-versus-skill help ownership boundary.

5.50  2026-10-03

  - Problem 35: configure Dist::Zilla's UploadToCPAN releaser so an explicit `dzil release`
    has a real PAUSE upload action; keep the upload separate from `dzil build`
    and document that credentials belong outside the repository.
  - Add a release metadata regression test for the configured Releaser plugin.

5.49  2026-10-03

  - Problem 12, 32, 33, and 34 delivery follow-up: publish the final Docker,
    coverage, package-build, and blank-container integration evidence in the
    numbered problem report.
  - Refresh the release archive to include the completed problem status index.

5.48  2026-10-03

  - Problem 12 follow-up: normalize dotted workspace refs consistently for
    tmux session lookup and creation, and reuse a duplicate session only after
    verifying the tagged logical workspace owner.
  - Problem 32: separate path aliases from command completion and bound cdr
    candidates to direct children, narrowing one directory per entered term.
  - Problem 33: add regression coverage confirming an unregistered leading
    cdr word remains a search term and participates in completion narrowing.
  - Problem 34: use invocation-directory Compose files as the base project and
    restrict automatic runtime service merging to services declared there.
  - Add Docker regression and complete statement, branch, condition, and
    subroutine coverage verification for the affected library.

5.47  2026-10-03

  - Problem 34: use invocation-directory Compose files as the local project
    base and limit automatic runtime service overlays to declared local
    services; keep explicit service selection and no-local-base discovery
    behavior intact.
  - Add Problem 33 regressions proving an unregistered first cdr argument
    remains a search term for both target resolution and later TAB narrowing.
  - Problem 12 follow-up: map dotted workspaces to tmux's underscore-normalized
    session names, check existing session ownership, and confirm duplicate
    create races before reuse.
  - Problem 32: keep path aliases out of root command completion, offer them
    beside tmux sessions for `workspace`, and restrict initial `cdr` TAB
    candidates and subsequent narrowing to one directory level per term to
    avoid recursive tree scans.
  - Add regression tests for completion namespaces, direct-child cdr listing,
    and concurrent workspace session creation; synchronize docs and POD.

5.46  2026-10-03

  - Align the main manual with the blank-environment integration behavior:
    plain cpanm runs the distribution tests, then installed d2 version and
    delegated grep/Compose help are verified against the archive.
  - Rebuild the final release artifact and Docker image after documenting the
    selected-home marker rule and installed short-entrypoint regression gate.

5.45  2026-10-03

  - Strengthen blank-environment release verification through the installed
    d2 entrypoint: compare its version with the tarball, verify delegated grep
    help, and preserve native Docker Compose config/help arguments.
  - Record explicit Problem 31 coverage that all Docker marker creation uses
    the selected home runtime while marker discovery/removal spans active
    service layers.

5.44  2026-10-03

  - Problem 12: Resolve workspace -c targets through configured aliases and
    skill Folder.pm methods at any nested skill depth, preserving config-first
    precedence and propagating real resolver errors.
  - Problem 25: Pass delegated grep and Docker Compose help requests through to
    their owning CLIs even when internal options or Compose selectors come
    first, while retaining Dashboard's explicit built-in help.
  - Problem 30: Parse standard five-field collector cron expressions from
    config.json, apply crontab day matching and named/ranged/stepped fields,
    deduplicate each minute, and reject missing or invalid schedules before
    launching a collector loop.
  - Problem 31: Honor disabled.yml and develop.yml markers from every active
    service config layer; service-enable and development-disable remove all
    matching markers across those layers.
  - Complete the 100% statement, branch, condition, and subroutine coverage
    gate across the full Perl library.
  - Run the blank-environment tarball install with plain `cpanm` so the
    packaged distribution test suite executes after installation.
  - Validate release installation with `cpanm`'s test phase enabled.
  - Add Docker regression coverage for skill workspace aliases and downstream
    help dispatch; synchronize CLI documentation and POD.
  - Enable an init reaper in the blank-environment test container so process
    tree assertions observe terminated descendants instead of unreaped zombies.

5.41  2026-10-02

  - Exclude all root-level Devel::Cover databases, including custom
    `cover_db_*` names, from distribution archives; add a release-metadata
    regression guard for the exclusion.

5.40  2026-10-02

  - Problem 27: Restore `dashboard of grep ...` as a recursive content search,
    using argv-safe grep execution and the existing file chooser; retain full
    Perl module source lookup across every directory in `@INC`.
  - Problem 28: Resolve collector `cwd` values from configured path aliases and
    skill-qualified `lib/Folder.pm` aliases, with config aliases taking priority.
  - Close the reachable four-metric coverage gaps found by the release gate,
    including grep editor/no-match dispatch and collector alias resolution.

5.38  2026-10-01

  - Problem 26 follow-up: preserve `origin/` in `dashboard ps1` unless a
    same-named local branch points to the exact same commit. Apply the rule to
    symbolic and detached HEADs, including loose and packed refs.

5.37  2026-10-01

  - Problem 26: preserve complete slash-delimited branch names in `dashboard
    ps1` for every shell adapter. Detached HEADs matching loose or packed
    `origin` refs retain the `origin/` prefix unless a same-named local branch
    points to the same commit;
    symlinked ref roots are ignored. Add regression coverage for both ref
    formats, symlink rejection, unmatched detached HEADs, and shared-renderer
    delegation from every supported shell bootstrap.

5.36  2026-10-01

  - Problem 7: resolve nested skill cli/__init__ entrypoints when addressed
    directly at any nesting depth. Prefer the deepest matching initializer
    after checking explicit commands, and document the dotted invocation.

5.35  2026-10-01

  - Problem 25: add a shared help catalog for every internal CLI command,
    compatibility alias, public action, and the direct version command.
    Route help before command execution and align TAB candidates with the same
    catalog, including nested actions and recognized option flags. Add concise
    global help, package/runtime regressions, and workspace completion that
    does not query tmux for option flags. Keep release metadata version-neutral.

5.34  2026-09-30

  - Complete Problem 24's Pod::Text 6.1.1 security floor and fail-closed
    isolated dependency audit guard. Keep the workflow release-gate assertion
    in the checkout-only metadata test so the packaged test suite does not
    require the intentionally unshipped .github tree. Problem 20 aggregate
    coverage remains paused.

  - Problem 20: bring lib/ to 100.0 percent statement, branch, condition and
    subroutine coverage. Add coverage tests t/300-t/305, t/330-t/331, t/360-t/362,
    t/390, t/420-t/422 and t/450-t/451; failure paths are injected with
    CORE::GLOBAL overrides so they run the same for root and non-root users.
    Genuinely unreachable branches carry their own uncoverable annotation.
    Fix scheme-relative bookmark targets (//host/path) dying on
    URI::_generic host(). Route the effective-uid check in SkillManager through
    _effective_uid so both answers are testable.

5.33  2026-09-30

  - Problem 24: Require Pod::Text 6.1.1 or later for CLI help rendering and
    enforce the fixed module path/version in the isolated dependency audit.
    Correct the CI audit step classification and guard the exact CPANSA
    podlators advisory against CPAN::Audit's independent Pod::Man version
    mapping. Keep Problem 20's aggregate coverage work paused.

5.32  2026-09-30

  - Correct Problem 21–23 report release labels and preserve the actual
    post-push 8.0/10 Scorecard result and its unresolved governance actions.
    Keep Problem 20 coverage paused.

5.31  2026-09-30

  - Correct the Problem 21–23 delivery report to reflect the pushed 5.30
    release and live 8.0/10 Scorecard result. Record remaining GitHub-side
    governance tasks explicitly; Problem 20 coverage remains paused.

5.30  2026-09-30

  - Complete the Problem 21–23 report with expected outcomes, Docker
    reproductions, root causes, current Git references, and explicit delivery
    status. Keep Problem 20 aggregate coverage paused as requested.

5.29  2026-09-30

  - Ship the final Problem 23 delivery report with the verified archive and
    image results instead of the pre-verification in-progress status.

5.28  2026-09-30

  - Exclude stale generated pax-output artifacts from source distributions so
    an old local binary cannot leak into the interpreted-only package.

5.27  2026-09-30

  - Exclude stale generated pax-output artifacts from source distributions so
    an old local binary cannot leak into the interpreted-only package.
  - Keep the blank-environment outsider-browser check portable across
    Chromium's generic 401 page and its HTTP response failure while continuing
    to assert that bootstrap guidance and the login form are not exposed.

5.26  2026-09-30

  - Remove the vendored PAX compiler, self-compilation cache, CLI command,
    private helper staging entry, and compiled-binary release workflow. Keep
    the interpreted Perl CLI as the single supported execution path, and add
    Docker regression coverage for the removed integration. Make the blank
    container's outsider-browser assertion accept Chromium's generic 401 page
    or its equivalent HTTP response failure while still rejecting leaked
    bootstrap content.

5.25  2026-09-30

  - Preserve eligible module source paths across standalone runtime-family
    expansion and payload mapping. Excluded compiled modules no longer mask
    runtime copies, duplicate modules are not re-added, and PAX-owned modules
    are packaged relative to their actual import root. Add Docker regressions
    for each collision/layout case and complete Problems 21 and 22 validation.

5.23  2026-09-30

  - Preserve known dependency source paths while expanding Perl package-family
    files, preventing a stale duplicate from being re-added to standalone
    runtime manifests. Add a Docker regression using matching module-relative
    paths.

5.22  2026-09-29

  - Complete Problems 21 and 22 delivery: verify both runtime roots
    participate together and keep ddfile.local dependencies private to their
    owning skill. Stabilize the standalone-image source-path regression with a
    unique fixture namespace. Confirm package tests in an init-enabled blank
    Docker container and update the Chromium headless SSL diagnostic checks.

5.21  2026-09-29

  - Complete delivery fixes for Problems 21 and 22: verify both runtime roots
    participate together and keep ddfile.local dependencies private to their
    owning skill. Update Docker regressions and packaged-test compatibility for
    newer Chromium headless certificate diagnostics.

5.20  2026-09-29

  - Discover both .d2 and .developer-dashboard runtime directories at each
    home and project depth, preserving canonical lookup/write precedence while
    allowing commands, config, Docker, dashboards, skills, and environment
    files from both roots to participate.
  - Install dependencies from an installed skill's ddfile.local into that
    skill's private skills/ tree instead of the runtime-wide skills root.
    Add Docker-run regressions for dual runtime roots and private dependencies.

5.18  2026-09-28

  - Preserve response headers set by skill Dancer2 before hooks when the
    adapter applies default security headers; explicit backend response
    headers retain final precedence. Document Dancer2 hook syntax and verify
    that request-local hook variables are readable in page and Ajax CODE.
    Confirm that skill Dashboard.pm code loads once at PSGI app startup.

5.17  2026-09-28

  - Add bookmark HEAD content for raw metadata/script/style injection into the
    rendered document head, with parsing, serialization, editor highlighting,
    and explicit trusted-content documentation.

5.16  2026-09-28

  - Correct skill install progress labels when ddfile or ddfile.local exists
    but no dependency install is needed; report a missing manifest only when
    the file is actually absent.

5.15  2026-09-28

  - Add -b/--branch to remote skill installs. Fresh installs try master then
    main by default; reinstalls keep the installed checkout's current named
    branch unless an explicit branch overrides it.

5.14  2026-09-28

  - Include each skill's configured and Folder.pm path aliases in d2 skill
    command completion after the skill-qualified prefix, loading only that
    skill's Folder.pm for the completion request.

5.13  2026-09-28

  - Added skill-provided path aliases through lib/Folder.pm. cdr and path
    resolve now check skill config aliases first, then call the matching
    Folder method. d2 paths, path list, and cdr completion merge names from
    Folder->__list__ without modifying skill files; path add continues to
    persist only in config/config.json.

5.12  2026-09-28

  - Added useful help output to d2 docker --help, dashboard docker --help,
    and the related -h/help/no-subcommand forms. Docker command completion now
    includes the development subcommand and its enable/disable actions, so the
    available nested commands can be explored interactively.

5.11  2026-09-28

  - Fixed skill dashboard CODE blocks and saved skill Ajax handlers so the
    active skill's ordered .env and .env.pl files are loaded into a scoped
    environment. Values now reach both page rendering and Ajax subprocesses
    without leaking into the web worker or other requests. Added PageRuntime
    and installed-skill route regressions for the documented FOO=BAR case.

5.10  2026-09-27

  - Fixed a `dashboard serve --foreground` startup hang for any skill
    declaring a collector: CollectorRunner::_find_running_loop scanned
    every /proc PID and fell back to spawning a `ps -o args= -p $pid`
    subprocess whenever a candidate's cmdline was readable-but-empty -
    the permanent, non-racy shape of a kernel thread, not just a
    vanished-process race. On a host with hundreds of kernel threads that
    cost one subprocess spawn per thread, sequentially, making the
    startup hang 90+ seconds with zero output. Now skips a
    readable-but-empty cmdline candidate before ever calling
    _read_process_title, since it can never match a collector's own
    non-empty process title.

5.09  2026-09-27

  - Fixed a dependency-scan masking defect: _locate_module_runtime_file
    walked @INC/roots and returned the FIRST existing file for a module
    name, discarding the dependency scan's own already-known source_path
    for that module. If an earlier @INC entry held a stale/duplicate copy
    of the module, the wrong file was silently bundled into a compiled
    standalone binary. _locate_module_runtime_file now accepts an optional
    known_source_paths lookup (built in _runtime_selected_files from
    %args{dependencies}) and prefers it over the @INC walk.

5.08  2026-09-27

  - Fixed a compiled-standalone-binary crash: three closures in
    StandaloneRuntime.pm's recursive merge/collect ops
    (config_merge_hashes, skill_dispatcher_merge_skill_hashes,
    suggest_collect_skill_commands) resolved their own recursion via
    _code_for($name), the bare unqualified sub name, instead of
    _code_for($full), the package-qualified name available in the same
    scope - _code_for() does a raw typeglob lookup, so the bare name
    resolved against the wrong package and returned undef, crashing
    `dashboard init`.

5.07  2026-09-27

  - Added explicit Docker development overlay toggles through
    `dashboard docker development enable|disable <service>` and the
    `develop.yml` service marker.
  - Kept each service `compose.yml` as the base and append
    `development.compose.yml` only when opted in; a missing opted-in overlay
    is skipped while the base remains active.
  - Added regression coverage for overlay ordering, marker commands, missing
    overlays, safe service paths, and PAX standalone runtime parity.

5.06  2026-09-27

  - Made the skill directory providing an `/app/<skill>/<page>` dashboard the
    first `@INC` entry for its CODE blocks, ahead of other layered skill libs.
  - Made the skill directory providing a Perl CLI script the first `@INC`
    entry and first skill library in `PERL5LIB`, ahead of generic dashboard
    and shared local Perl libraries.
  - Added tests that print and check the actual `@INC` from dashboard CODE and
    a dispatched Perl CLI script; both verify the page/command skill's `lib/`
    directory comes first.
  - Excluded and pruned generated `pax-output/` binaries from distribution
    scans and tarballs; an ignored 90+ MB compiled dashboard and deep excluded
    trees had made `dzil build` spend minutes walking build output.

5.05  2026-09-27

  - Fixed transient Ajax URL generation to encode the rendered Template Toolkit
    code instead of the original template source. For example, data containing
    `args => 123` now turns `my $foobar = [% args %];` into executable Perl
    before tokenization. Added regression coverage for the generated payload.
  - Documented templating behavior for both transient URLs and saved Ajax files.

5.04  2026-09-26

  - DD-1059: added the missing full-POD-DOC block to
    t/226-pax-paxfile-parser.t (a race in the shared main checkout meant
    this fix landed only as an uncommitted working-tree change before
    5.03 was tagged - see FIXED_BUGS.md).

5.03  2026-09-26

  - Fixed a background-job grandchild surviving the process-group SIGKILL in
    `Local::BoundedCommand::run_bounded` (t/lib/, test harness) when run
    inside a container started without an init that reaps orphans - the
    kind of container this project's own gate scripts use. The group kill
    already reached and killed the grandchild; nothing ever reaped it once
    its immediate parent died, so it was a permanent zombie and `kill 0`
    kept reporting it alive. `run_bounded` now declares itself a Linux/
    x86_64 subreaper before forking (best-effort, a documented no-op
    elsewhere), and the cleanup sweep now runs on both its polite (TERM)
    and escalated (KILL) exit paths - the first version of the fix only
    swept on the escalated path and missed the common case where TERM
    alone already finishes the job. (DD-1051)
  - Documented the mechanism at docs/bounded-command-orphan-reaping.md.
  - Filed DD-1067 for the same defect class found, independently and
    pre-existing, in PageRuntime's own `_terminate_saved_ajax_process`.

5.02  2026-09-26

  - DD-1059: brought lib/Developer/Dashboard/Pax/Paxfile.pm to 100.0 percent
    Devel::Cover on all four metrics (statement, branch, condition,
    subroutine), up from 11.5 percent. Added t/226-pax-paxfile-parser.t (17
    assertions) covering every load_optional/load/_scalar code path,
    including the open()-failure branch and both directions of the
    defined-or section-reuse condition. Added missing purpose/input/output
    comments to all three subroutines.

5.01  2026-09-26

  - Resolved bare skill-qualified workspace aliases (for example,
    `bar.foo`) without requiring `-c`, starting the tmux session in the
    registered directory.
  - Automatically imported the namespaced DataHelper JSON functions in every
    saved-page CODE block.
  - Loaded trusted skill `lib/Dashboard.pm` extensions into the shared Dancer2
    app at web startup, including on restart and serve commands, with routes
    behind the dashboard authorization gate.
  - Excluded generated PAX test executables from distribution scanning so
    Dist::Zilla no longer decodes binary scratch output as UTF-8 source.
  - Marked inline helper packages as no-index metadata so the provides scan
    does not warn that they lack standalone module files.
  - Added focused regression coverage and operator documentation for all three
    changes.

5.00  2026-09-26

  - Prepared the 5.00 release metadata and synchronized module versions,
    POD, and the generated README.

4.99  2026-09-26

  - Made the final checkout-only coverage workflow gate skip cleanly in
    installed release tarballs where .github/ is intentionally excluded.

4.98  2026-09-26

  - Made source-checkout-only release gates skip cleanly in installed tarballs,
    added UTF-8 POD metadata to the non-ASCII regression test, and removed a
    coverage-gate warning when checkout paths are unavailable.

4.97  2026-09-26

  - Added skill C<lib/> directories to skill CLI C<PERL5LIB> and skill page
    CODE C<@INC> so both command scripts and dashboard CODE blocks can load
    modules shipped by their skill.
  - Loaded skill C<cli/.env> files and changed skill-command env precedence so
    the invocation cwd C<.env> is applied last and can override skill values.
  - Allowed cwd C<.env> loading even when C<d2> is launched outside the home or
    project-root ancestry.
  - Rendered skill C<cli/__init__> completion entries as the bare skill name
    instead of C<skill.__init__>.
  - Removed the public C<ticket> helper alias in favour of C<workspace>.
  - Added regression coverage for multi-level nested skill command resolution.
  - Fixed checkout-only GitHub workflow validation so packaged tarball installs
    skip it cleanly when C<.github/> is intentionally excluded from the
    release payload.

4.95  2026-09-25

  - Merged incoming query parameters into saved skill bookmark URLs, replacing
    matching keys while preserving saved parameters and appending new values.

4.94  2026-09-25

  - Fixed skill bookmark files containing saved HTTP(S) URLs so they forward
    to the target instead of being parsed as instruction documents.

4.93  2026-09-25

  - Fixed saved URL bookmark forwarding so instruction parsing errors are not
    leaked when a legacy entry is routed through named-page loading.

4.92  2026-09-25

  - Bumped the release metadata and every library version to 4.92.
  - Restored legacy saved URL bookmark compatibility: external HTTP(S)
    bookmarks redirect with the incoming query, local routes forward
    internally, and legacy selected array parameters are resolved.
  - Made Template Toolkit bookmark includes skill-aware, supporting both
    relative skill dashboard fragments and explicit skills/<name>/dashboards
    include paths.
  - Made saved Ajax C<code> templates render their supplied C<data> values
    before the generated handler is stored.
  - Kept dashboard-owned init helpers inside the private C<cli/dd> namespace
    for both C<dashboard init> and C<d2 init>, without overwriting user CLI files.
  - Nested skill CLI commands now inherit the DD-OOP runtime env chain and
    every parent skill's C<.env> and C<.env.pl> before the leaf command runs.

4.91  2026-09-25

  - Raised the Dancer2 dependency floor to 2.2.0, the first release outside
    the newly reported vulnerable ranges.

4.90  2026-09-25

  - DD-1050: removed 'lib' from _skip_dependency_module's skip-list
    regex in StandaloneImage.pm. Unlike strict/warnings (true
    compile-time-only pragmas), lib.pm is a real, physically-requirable
    module whose import() runs real code (unshift @INC) - any 'use lib'
    reached through StandaloneRuntime.pm's require wrapper at runtime
    needs the actual lib.pm file bundled. Confirmed live via a real
    GitHub Release linux-amd64 binary (commit bf1c4ccc/v4.87) crashing
    --help and jq with "Can't locate lib.pm in @INC" in a hostile env -i
    container. Same class of defect as DD-1022 (overload).

4.89  2026-09-24

  - DD-1052: quoted every status-format[N] literal in the zsh/bash/sh
    POSIX-family shell blocks of _dd_apply_tmux_ticket_status inside
    share/private-cli/_dashboard-core. An unquoted status-format[N]
    is treated as a glob pattern by zsh, which aborts the shell hook
    with a NOMATCH error under zsh's default options - crashing the
    tmux ticket-status integration on macOS, whose default shell is
    zsh. PowerShell block left unquoted (different argument-parsing
    model, does not reproduce the bug).

4.88  2026-09-24

  - DD-1029 follow-up: added the standard purpose/input/output header
    comment to _declared_modules (missed in the original overloading.pm
    fix), and extended the docs vault page explaining the
    overload.pm -> overloading.pm transitive-dependency discovery gap.
    No executable change - the functional fix already shipped in v4.87.

4.87  2026-09-24

  - DD-1035 follow-up: fixed a real regression in v4.86's own fix, caught
    by real GitHub Actions CI before this ticket reached a terminal
    column (Test workflow's "Run tests" step, t/183's "self-built pax
    runs without source checkout in its working directory"). v4.86 made
    $PAX_OWN_LIB_ROOT's exclusion from the normal by-@INC selection
    genuinely work for the first time - correct for hybrid_compiled_pcu_v1
    dependencies (that's what it fixed) - but the same exclusion also
    stopped bundled_pure_perl/bundled_xs dependencies whose real
    source_path happens to live under that root (e.g.
    Developer::Dashboard::Pax::CLI, hit when the pax tool compiles
    itself - a genuine self-hosting case). Widened the force-include
    mechanism to cover any dependency living under $PAX_OWN_LIB_ROOT,
    not only hybrid_compiled_pcu_v1-packaged ones. New regression test
    AC-5 in t/223. Reproduced and fixed locally using the exact
    hostile-environment condition t/183 constructs (env -i, no
    inherited PERL5LIB/@INC) before landing.

4.86  2026-09-24

  - DD-1035: fixed a real, published-release-breaking bug where a
    standalone PAX binary could crash with "Can't locate
    Developer/Dashboard/SeedSync.pm in @INC" on --help/jq (any subcommand
    reaching a hybrid_compiled_pcu_v1-packaged dependency). Two coupled
    defects in StandaloneImage.pm: (1) $PAX_OWN_LIB_ROOT was computed via
    File::Spec->catdir(...,updir,updir,updir) with no abs_path() wrap, so
    its own exclusion of this checkout's lib/ from runtime_inc bundling
    was silently a no-op; (2) the force-include mechanism for
    hybrid_compiled_pcu_v1 dependencies only ever protected an
    already-selected file from exclusion, never added a file the normal
    by-@INC selection failed to find. Fixed both together (fixing either
    alone would have made things worse). Affects all 46
    hybrid_compiled_pcu_v1-packaged modules in this project, not just
    SeedSync. Never reproduced on any local/container host this ticket
    was diagnosed on - every one happened to have a stray installed copy
    of this project's own package somewhere on @INC, masking the gap that
    only a genuinely clean GitHub Actions runner exposes. New regression
    test t/223 (4 assertions), 2 branches genuinely unreachable and
    annotated. Verified clean on Alpine (musl libc, distinct Perl
    version) in addition to the Debian-based gate container.

4.85  2026-09-23

  - DD-1010: bin/dashboard's self-compile mechanism (DD-882) now actually
    execs a resolved PAX cache hit instead of discarding it, via the same
    _exec_switchboard_command() the ps1 allowlist already used. Re-enabled
    once DD-930's own root cause (a CodeUnitCompiler EnvLoader.pm
    special-case producing an unreachable EnvAudit->record call in the
    compiled binary) shipped. t/182 updated (11/11 PASS, including a real
    compiled-binary regression run). Full suite verified clean in a
    container - 9 pre-existing environmental failures found and
    independently confirmed unrelated to this change (cpan-audit missing,
    git-worktree-in-container, Devel::Cover missing from the dev image).
    Real timing measured: cold PAX compile 86.8s, interpreted "version"
    165ms, self-exec warm run 129-138ms, the same compiled binary invoked
    directly 2-4ms - the compiled binary itself is 40-80x faster, though
    bin/dashboard's own outer interpreted startup currently dominates the
    end-to-end warm-path cost for a trivial command.

4.84  2026-09-23

  - DD-1021: t/09-runtime-manager.t and t/105-web-app-coverage-2.t now
    pass in a root, ss-less, file-less container - t/09 stubbed
    command_in_path('ss') alongside the existing capture() mock; t/105
    uses an attempt-based (not identity-based) skip for its
    unreadable-file assertion, matching t/78's established pattern.
  - DD-1043: d2 <skill>.version now natively reads VERSION= from a
    skill's own .env when the skill ships no cli/version script,
    instead of requiring every skill to hand-write one.
  - DD-1044: EnvLoader::_load_env_pl_file now records a .env.pl's
    same-value re-assignment ($ENV{KEY} set to the value it already
    had) in DEVELOPER_DASHBOARD_ENV_AUDIT - a pure before/after value
    diff could never see this case.
  - DD-1009: Pax::ArtifactCache::write_artifact now writes its
    cache-metadata JSON atomically (temp path + rename), closing the
    same truncate-on-interrupt defect class already fixed in DD-989
    and DD-1003.
  - DD-1023: release-github.yml and test.yml now install binutils
    explicitly before their build-dependent steps, fixing t/183's
    self-hosted pax-build scenarios on a runner missing `as`.

4.83  2026-09-22

  - DD-1015: real Windows amd64 PAX build wired into pax-release.yml,
    replacing the placeholder - MinGW-w64's real GNU objcopy supports
    the same binary-to-object conversion the Linux path already uses,
    targeting PE-COFF. windows-arm64 stays a placeholder pending
    verification of binutils' PE-ARM64 target name against the real
    tool.
  - DD-1025: the GitHub Release page only ever attached the source
    tarball - the compiled PAX binaries existed only as ephemeral
    workflow-run artifacts, not discoverable on the release page and
    subject to expiry. Fixed by calling pax-release.yml as a reusable
    workflow from release-github.yml and attaching its build outputs
    to the release as real assets.

4.82  2026-09-22

  - DD-1022: `_skip_dependency_module` wrongly treated `overload` as a
    compile-time-only pragma like strict/warnings/utf8, so it was
    never bundled into a standalone binary's runtime payload. Any
    subcommand whose code path reached File::Temp (which itself
    `use overload`s stringification/numification) crashed at BEGIN
    time with "Can't locate overload.pm in @INC" - shipped in v4.78/
    v4.79 undetected because the release smoke-check only ever ran
    `dashboard version`. Fixed by removing `overload` from the skip
    list.

4.81  2026-09-22

  - DD-1020 (real fix for t/183, found via real CI on v4.80): the
    compiler probe added for DD-1020's objcopy architecture detection
    ran BEFORE _compile_launcher's own PATH restoration was applied,
    so it failed under a caller that deliberately strips $ENV{PATH}
    (t/183's own standalone-execution test) - misdiagnosed as DD-1023
    ("as"/binutils missing from the runner, which was never actually
    true). Fixed by moving the probe inside the same PATH-restoration
    scope the objcopy/cc calls already use.

4.80  2026-09-22

  - DD-1019: t/159 and t/185 were failing for real, deterministic reasons
    (not CI-load flakiness as first assumed) - a stale $?-guard baseline
    entry, 2 genuinely-unguarded local $?; gaps, a heredoc blind spot in
    the sweep's own comment-stripper, and t/185 missing the DD_PAX=on
    opt-in that a later change required. All fixed and verified.
  - DD-1011: t/43's usage-string regexes for 'file add'/'path add' were
    stale since DD-1005 added the -c/--create flag. Corrected.
  - DD-1023: release-github.yml and test.yml's runners lacked a working
    assembler (as), breaking t/183's self-hosted pax-build scenarios.
    Fixed by installing binutils explicitly.

4.79  2026-09-22

  - DD-1020 (correction, same day as v4.78's fix): the archname-based
    objcopy architecture detection was itself wrong for linux-i686
    cross-compilation - a real GitHub Actions run confirmed linux-arm64
    genuinely fixed (native aarch64 runner) while linux-i686 still
    failed, because `$Config{archname}` reflects the Perl interpreter's
    own build, not a `-m32`-wrapped cross-compile target. Replaced with
    a real compiler probe (`_compile_probe_object_header`) that compiles
    a trivial source with the actual `cc` and reads back the genuine
    ELF class/machine the result carries, honoring any PATH wrapper.

4.78  2026-09-22

  - DD-1020: `_compile_launcher`'s objcopy invocations previously hardcoded
    an x86_64 `--output`/`--binary-architecture` spec unconditionally,
    breaking non-x86_64 PAX build hosts - confirmed live in real CI, where
    linux-arm64 and linux-i686 both failed at the objcopy step while
    linux-amd64 succeeded fully. Added `_objcopy_target_for_arch`, which
    detects the build host's real architecture from `$Config{archname}`
    and selects the correct objcopy target pair, verified against the
    real objcopy/binutils tool for x86_64/i686/aarch64. Unblocks DDE-006's
    linux-i686 and linux-arm64 multi-platform PAX CI targets.

4.77  2026-09-22

  - DD-1018 (follow-up): a real PAX Release CI run of v4.76's two prior
    fixes surfaced a third defect - `share/private-cli/pax`'s FindBin-
    based lib resolution failed in the actual GitHub Actions environment
    ("Can't locate Developer/Dashboard/Pax/CLI.pm"), though every local
    invocation this session ran succeeded. Fixed by passing `-Ilib`
    explicitly on the `perl` invocation in `pax-release.yml`'s Linux
    build step, sidestepping FindBin+cwd inference entirely.

4.76  2026-09-22

  - DD-1018: `pax-release.yml`'s Linux matrix jobs now bootstrap Perl and
    `cpanm` via the same `shogo82148/actions-setup-perl` step `test.yml`
    already uses (real CI runs failed with "cpanm: command not found"
    otherwise). Also re-pinned `actions/upload-artifact` from v4.6.2
    (node20, below this project's enforced node24 floor) to v7.0.1
    (node24-verified) - a second real defect found via the same live CI
    investigation. Every real PAX Release workflow run failed from one
    of these two causes until now.

4.75  2026-09-22

  - DD-1017: `bin/d2`'s dispatch line unconditionally routed through the
    perl interpreter to invoke its sibling `dashboard` file, assuming it
    was always Perl source - crashing whenever that sibling was itself a
    PAX-compiled standalone binary. Fixed by detecting compiled-vs-
    interpreted via the sibling's first two bytes (Perl source always
    opens with a `#!` shebang, a compiled binary never does) and
    execing the compiled binary directly with no perl interpreter when
    appropriate. The existing interpreted-source dispatch path is
    completely unchanged.

4.74  2026-09-22

  - DD-1013: wired `.github/workflows/pax-release.yml`'s linux-amd64/
    arm64/i686 matrix jobs (part of epic DDE-006) to a real `dashboard pax
    build` invocation and a native smoke check comparing the compiled
    binary's own `version` output against the source-Perl CLI's, failing
    the job on any divergence. linux-i686 installs gcc-multilib and
    routes around `_which('cc')`'s hardcoded PATH resolution (it never
    reads `$ENV{CC}`) with a PATH-prepended wrapper forcing `-m32` -
    caught and fixed a self-recursion bug in that wrapper before landing
    (it execed the bare name `cc` instead of an absolute path, so it
    called itself forever). d2 is deferred pending DD-1017 (bin/d2's
    re-exec line cannot currently dispatch to a compiled dashboard
    binary, found live while implementing this ticket).

4.73  2026-09-22

  - DD-1012: added `.github/workflows/pax-release.yml`, part of epic
    DDE-006 (Multi-platform PAX CI) - a push-to-master workflow skeleton
    matching `package-ghcr.yml`'s established trigger convention, with a
    6-entry build matrix covering the owner-confirmed platform/arch
    targets (mac arm64; linux arm64/amd64/i686; windows arm64/amd64) and
    per-target artifact-upload wiring. The real `dashboard pax build`
    invocation and functional verification per platform are deferred to
    sibling tickets under the same epic.

4.72  2026-09-22

  - DD-1002: nine `lib/Developer/Dashboard/Pax/*.pm` modules each hand-rolled
    their own `JSON::XS->new->...->encode()` chain with independently
    drifted option sets, instead of using the shared
    `Developer::Dashboard::JSON` wrapper the rest of the codebase already
    uses. Added `json_encode_with_options($value, %opts)` supporting the
    ascii/pretty/utf8 combinations these call sites need, and converted all
    nine sites (`ArtifactCache.pm`, `AppImage.pm`, `AppServer.pm`, `CLI.pm`,
    `CodeUnitCompiler.pm` (6 sites), `ProfileGuidedAOT.pm`,
    `StandaloneImage.pm`) - `Pax/Capture.pm`'s isolated child-probe call
    site is a documented, deliberate exception (runs with no guaranteed
    `-I lib` path).

4.71  2026-09-22

  - DD-1006: `_compile_launcher` in `Pax::StandaloneImage` ran a build eval
    followed by a cwd-restore eval and read `$@` once for both failure
    paths' `reason` field. Perl resets `$@` at the start and on successful
    completion of every eval, so when the build eval failed but the
    subsequent restore eval succeeded (the common case), the restore
    eval's success silently erased the real build-failure diagnostic
    before it was ever read - a failed standalone-image build reported an
    empty reason. Fixed by capturing each eval's `$@` into its own
    variable immediately after that eval returns, before the other eval
    can run and clobber it.

4.70  2026-09-22

  - DD-1005: `d2 path add`/`d2 file add` now accept a `-c`/`--create` flag
    (with an optional octal chmod mode, in bare/space-separated/=-form/
    short-flag syntax) marking an alias as lazily creatable. The first
    time anything resolves a lazy-create alias whose target is missing,
    it is created - a directory (with parents) for a path alias via
    `PathRegistry::resolve_dir`, or just the parent directory chain for a
    file alias via `FileRegistry::resolve_file` (per Q-184: a file
    alias's own target file is left absent, since the caller intends to
    write it). The create/mode metadata composes cleanly with DD-1004's
    nested skill-depth alias storage, and is fully backward compatible -
    an alias saved without `-c`/`--create` stores and reads back exactly
    as a plain path string, as it always did.

4.69  2026-09-21

  - DD-1003: `PaxCache::_run_compile_and_install`'s `md5_file` digest write
    is now atomic (write to a sibling temp path, then `rename()`), matching
    the atomic-install pattern already used for `bin_file` two lines above
    it. Previously a plain `open('>', $md5_file)` truncated the file to
    zero bytes the instant `open()` succeeded, well before the digest was
    written - any interruption in that window (SIGKILL, OOM, host reboot)
    left `md5_file` as a real, existing, zero-byte file beside an
    already-valid, already-renamed binary, permanently wedging
    `resolve()` into treating a fresh cache as stale forever.
    `t/206-paxcache-md5-atomic-write.t` proves the fix directly via a
    fork+SIGKILL race against the real `_run_compile_and_install` path.

4.68  2026-09-21

  - DD-1004: `d2 path add`/`d2 file add` now support skill-depth-prefixed
    dotted alias names (e.g. `d2 path add foo.bar.something /foobar`),
    resolving arbitrarily nested `skills/<a>/skills/<b>/...` directories.
    The write NEVER targets a skill's own `config/config.json` directly, at
    any depth, regardless of whether that skill carries its own `.git`
    (owner correction, Q-182, generalising the original git-preservation
    framing): it always walks up to the nearest git-free PARENT ancestor,
    or the global config's `skills` fallback section when none exists
    (always true for a depth-1 name, which has no parent skill) - storing
    the alias as a NESTED hash mirroring the remaining depth segments,
    never a flattened dotted string. A user's override this way always
    shadows a skill's own shipped default at read time without ever
    modifying that skill's file, letting a skill's shipped config survive
    every future `d2 skill install`/update untouched. New:
    `PathRegistry::nested_skill_dir_chain`/`nested_skill_entries`/
    `skill_config_write_location`; `Config::split_skill_alias_name`,
    `save_skill_path_alias`/`save_skill_file_alias`,
    `remove_skill_path_alias`/`remove_skill_file_alias`, and the top-level
    `save_path_alias`/`save_file_alias`/`remove_path_alias`/
    `remove_file_alias` dispatchers both CLI::Paths and CLI::Files now
    route through. An undotted alias name keeps today's exact global-config
    behavior, byte-for-byte unchanged. Added t/205-skill-depth-alias.t.

4.67  2026-09-21

  - DD-989: fixed a symlink-clobber vulnerability in
    Developer::Dashboard::IndicatorStore::set_indicator. Its staging path
    used to be the bare literal "$file.pending" with no pid/time/counter
    uniquifier at all, and permissions were secured only AFTER the write -
    the exact write-then-secure anti-pattern already fixed six other times
    (DD-599/600/601/602/848/850), but worse: a fully predictable path,
    missed by both prior sweeps because neither searched for a
    pending-file writer with no uniquifier whatsoever. Live-reproduced: a
    symlink pre-planted at the predictable staging path let a local actor
    clobber an arbitrary victim file's content and leave the real
    status.json pointing at the victim afterward. Fixed by routing through
    the shared Developer::Dashboard::PathRegistry::atomic_write_secure
    helper via a new _pending_indicator_file staging-path helper (pid +
    time + a per-process monotonic counter), matching the pattern already
    used by SessionStore.pm/Auth.pm/Collector.pm/CLI/Ask.pm/Zipper.pm.
    Added t/204-indicatorstore-pending-path-symlink.t, a regression test
    that plants a symlink at the historical predictable path and asserts
    the victim file is untouched and the real indicator file ends up a
    regular file with correct content. Updated
    docs/pending-file-staging-paths-across-the-codebase.md to add
    IndicatorStore as the 7th migrated site. Filed DD-999 (pre-existing
    500-line module-size overage) and DD-1000 (unaudited lock-file symlink
    exposure, low severity) as separate, out-of-scope follow-ups found
    during this ticket's audits.

4.66  2026-09-21

  - DD-993: fixed a CI-only, warnings-FATAL build failure ("Prototype
    mismatch: sub Developer::Dashboard::RuntimeManager::sleep (;$) vs
    (;@)") in t/100-runtimemanager-coverage.t and
    t/106-runtimemanager-coverage-2.t. RuntimeManager.pm imports its
    "sleep" from Time::HiRes, so RuntimeManager::sleep is an alias whose
    prototype is whatever the running Perl's Time::HiRes gives sleep() -
    "(;$)" on the Perl 5.44 GitHub Actions used, "(;@)" on the Perl 5.40.1
    this project's own Docker image bundles, which is why the bug was
    invisible locally. Both coverage files installed their test-only mock
    of RuntimeManager::sleep as a permanent (non-local) typeglob
    assignment guarded only by "no warnings 'redefine'", which does not
    cover Perl's separate 'prototype' warning category - even an
    unprototyped override mismatches "(;$) vs none" once only 'redefine'
    is silenced. Fixed by silencing both categories together
    ("no warnings qw(redefine prototype);") and dropping the mock's own
    explicit prototype. Added
    t/203-runtimemanager-sleep-mock-prototype.t, a deterministic,
    Perl-version-independent regression test for the mechanism, and a new
    system-scoped section in docs/expected-warnings-in-tests.md on
    overriding subs imported (not locally declared) from another module.

4.65  2026-09-21

  - DD-941: t/15-release-metadata.t's retired-internal-wording gate
    (unlike $doc, qr/\blegacy\b/i) correctly bans that retired wording,
    case-insensitively, in shipped docs/POD, but FIXED_BUGS.md's
    append-only DD-931 (4.37) and DD-933 (4.42) entries legitimately
    reference the real, current,
    shipping identifier __PAX_RUNTIME_LEGACY_NAMESPACE__ and describe it in
    prose as a "legacy-namespace alias" - the DD-931 entry uses that phrase
    without the literal identifier present on the same line, so an
    identifier-only exemption did not close the gate (owner decision,
    Q-179). Widened the allow-list (a new _strip_legacy_namespace_mentions
    helper in t/15-release-metadata.t) to also strip the phrase
    "legacy-namespace" case-insensitively before the check runs, alongside
    the existing identifier exemption, while a genuinely retired bare
    mention of that same banned term anywhere else still fails the gate.
    No FIXED_BUGS.md entry was reworded.

4.64  2026-09-21

  - DD-926: pax build's intermediate build cache directory
    (.pax-launcher-build, under Developer::Dashboard::Pax::StandaloneImage's
    _compile_launcher) was a single literal path shared by every uid that
    ever built against the same output parent directory (commonly bare
    /tmp). A root-owned build (e.g. from inside a developer-dashboard:latest
    container) left it root-owned, permanently breaking every later
    non-root build sharing that output parent with a Permission Denied that
    did not self-clear. Namespaced the directory per invoking uid
    (.pax-launcher-build-<uid>, via a new _pax_launcher_build_dir_name
    helper), the same technique File::Temp uses to avoid this exact
    shared-/tmp collision class. Cache reuse within one uid's repeated
    builds is unchanged.

4.63  2026-09-21

  - DD-986: CLI/Skills.pm kept its own private _render_table/_format_row,
    left out of DD-773's consolidation of Files.pm/Paths.pm/Which.pm onto
    the shared Developer::Dashboard::CLI::TableHelpers::render_table.
    Confirmed via grep of every real call site that its ANSI-stripping
    behavior (_plain_text) was dead weight - the only content-producing
    helpers feeding cells return hardcoded plain text. Migrated Skills.pm
    onto the shared helper, removed ~40 lines of duplicated code, verified
    byte-identical output for representative skill-list data.

4.62  2026-09-21

  - DD-984: Developer::Dashboard.pm's "Layered Env Files" POD section had
    no cross-reference to DD-979's env->include/# include feature,
    documented ~2500 lines later with no backlink either. Added a pointer
    paragraph in both directions; README.md regenerated.

4.61  2026-09-21

  - DD-985: fixed a path-traversal vulnerability (CWE-22) in
    'dashboard of'/open-file's exact-match fast path
    (_scope_relative_path_match). A '..' segment in the search
    pattern escaped the resolved scope directory entirely, so
    ordinary usage such as 'dashboard of myproject ../../.ssh
    id_rsa' could open or print any file readable by the invoking
    user, with no special flag required. Fixed by resolving through
    the project's existing lexical containment helper
    (_contained_cache_path, the same one DD-498's Zip Slip fix
    established) instead of a second bespoke check. The
    regex-search fallback path was confirmed structurally
    non-vulnerable and left untouched.

4.60  2026-09-21

  - DD-979: new .env/.env.pl skill include feature. A "# include
    <foo.bar>" comment directive in a plain .env file (or "# include
    <foo.bar.*>" to also pull in every nested sub-skill) loads that
    skill's own .env/.env.pl into the current environment on request;
    env->include('foo.bar') does the same programmatically from a
    .env.pl file after `use Developer::Dashboard;`. env is a real
    second package (Perl resolves a bareword before -> as a literal
    package name at parse time, so it cannot be a plain exported sub
    like d2). Included variables are namespaced
    <UPPERCASE.DOTTED.PATH with single underscores>__<name> (foo.bar's
    BOB=1 becomes FOO_BAR__BOB), resolved DD-OOP-LAYERS-style across
    every layer with deeper layers winning without dropping
    shallower-only keys. Hardened against a path-traversal exposure
    found during implementation (a spec like "../.." surviving the
    dot-split as a bare "/" segment) with an explicit _valid_segment()
    guard. New module Developer::Dashboard::EnvInclude, 100.0/100.0/
    100.0/100.0 coverage, 42 new tests, new SYSTEM-scoped doc
    docs/env-skill-include-directive.md.

4.59  2026-09-21

  - DD-978: the parallel gap to DD-977, for file_aliases instead of
    path_aliases - a skill's own config/config.json file_aliases block
    was completely invisible to cdr/d2 paths. Added
    Config::_skill_file_aliases() (an exact parallel to DD-977's
    _skill_path_aliases()), merged into file_aliases(). Same
    skill-name qualification convention, same OOP-LAYERS-safe-by-
    construction merge behavior. Verified with 20 new tests and a
    live end-to-end docker probe.

4.58  2026-09-21

  - DD-977: a skill's own config/config.json path_aliases block was
    completely invisible to cdr/d2 paths - Config::path_aliases()
    only ever read the top-level project/global merged config key.
    Added Config::_skill_path_aliases(), which merges in each
    installed skill's own path_aliases, qualified by skill name
    (skill.alias). Verified OOP-LAYERS recursive merge with two real
    layers of one skill. file_aliases has the identical gap, tracked
    separately as DD-978.

4.57  2026-09-19

  - DD-964: docs/pax-coverage-measurement.md's follow-up-work pointer
    cited DD-929, which is now closed (done-not-released) and never
    did that follow-up. Filed DD-967 as the real open follow-up ticket
    and updated the doc to cite it instead.

4.56  2026-09-19

  - DD-929: lib/Developer/Dashboard/Pax/*.pm was entirely absent (zero
    rows) from every Devel::Cover run - a PAX-compiled binary's extraction
    root gets spliced onto PERL5LIB before exec, and Devel::Cover's
    default startup behavior silently ignores anything already on @INC
    at that point. The extraction path was already stable
    (content-addressed by source_hash); the missing piece was telling
    Devel::Cover to -select it explicitly. Added
    Developer::Dashboard::Pax::CoverageSelect (4 tested helper
    functions) and t/200-pax-coverage-select.t (32 tests, including a
    live build-and-run integration check), plus
    docs/pax-coverage-measurement.md documenting the mechanism.

4.55  2026-09-18

  - DD-958: Platform.pm's resolve_runnable_file (lines 183-184) and
    command_argv_for_path (line 226/227's Windows fallback) each had an
    untested direction, on both the forced-Windows side and its natural
    Linux counterpart - Devel::Cover's true/false columns track the
    literal written condition, not its display-rewritten "unless" text.
    Added 4 targeted test assertions to t/84-platform-coverage.t (2
    forced via the project's established local $OS_NAME override, 2
    natural-Linux counterparts); Platform.pm now 100.0 on all 4
    Devel::Cover metrics. New SYSTEM-scoped page
    docs/forced-windows-unit-tests.md documents the override convention.

4.54  2026-09-18

  - DD-928: Zipper.pm's load_saved_ajax_code open()-failure branch now
    carries a correct `# uncoverable branch true` annotation (it had
    none), restoring 100.0 branch coverage - the failure path is only
    reachable as a non-root user, and gate containers run as root.
    Extends docs/uncoverable-annotations.md with the underlying
    mechanism: Devel::Cover rewrites `EXPR or STATEMENT` as
    `unless(EXPR){STATEMENT}`, inverting true/false from the naive
    reading.

4.53  2026-09-18

  - DD-953: measured PAX self-compiled vs interpreter speed for the 7
    data-query CLI commands (jq/yq/tomq/propq/iniq/csvq/xmlq) against an
    equivalent ~50,000-record fixture, in a developer-dashboard:latest
    container - documented in docs/pax-alpine-and-runtime-speed-measured.md.
    Finding: the compiled binary was slower on every command (1.2x-6.4x),
    the opposite of the earlier dashboard-version benchmark - a real
    parsing workload's own cost dominates the standalone binary's
    unpacking overhead. No code changes; a measurement/documentation
    ticket only.

4.52  2026-09-18

  - DD-951: skills using Go now get per-layer module/build cache
    isolation - `_exec_go_source` sets GOMODCACHE/GOCACHE to a skill
    layer's own `local/go-cache/` directory whenever a sibling go.mod is
    found, so two skills declaring different Go module versions never
    share one global cache. Mirrors DD-823 (Java/Maven) and DD-824
    (Python/venv). A skill with no go.mod is unaffected.

4.51  2026-09-18

  - DD-946: `dashboard ask`'s direct-API backend (--claude, with a key
    set) now runs a real Anthropic tool_use round-trip loop instead of a
    single non-streaming POST, offering two read-only tools (read_file,
    grep_repo) scoped to the current project root - closing the gap
    where only the CLI-fallback path could answer genuinely
    repo-specific questions. A plain question needing no repo search is
    unchanged (one request, same shape as before).

4.50  2026-09-18

  - DD-952: added Amazon Nova as a dashboard ask backend (--nova), against
    Nova's own standalone REST endpoint (api.nova.amazon.com/v1/chat/
    completions, bearer-token auth via NOVA_API_KEY) - the same direct-API
    shape --claude already uses, not AWS Bedrock/SigV4.

4.49  2026-09-18

  - DD-954: a skill's own cli/__init__.<ext> self-script now runs when
    it is invoked with no subcommand at all - d2 <skill-name> (bare) and
    d2 <skill>.<nested-skill> (dotted, when no explicit subcommand file
    exists) both fall back to it, any extension. An explicit top-level or
    subcommand always wins over the fallback.

4.48  2026-09-18

  - DD-948: dashboard ask's CLI-fallback backend path (codex/copilot/gemini,
    or claude with no API key set) now tees its child process's output
    live to the caller's terminal instead of fully buffering it until the
    process exits. Switched _run_cli from Capture::Tiny's capture{} to
    tee{}. The direct-API path is unchanged (real streaming there needs
    Anthropic SSE wiring, a separate, larger change).

4.47  2026-09-18

  - DD-950: excluded a stray NYTProf profiling artifact (nytprof.out) from
    the release tarball. dzil's GatherDir gathers from disk, not git, so an
    untracked, ungitignored leftover file ships exactly like a real source
    file - the same mechanism that let FIX.md leak into the 4.22 tarball
    (DD-401). Found auditing DD-947's 4.46 build. Added dist.ini exclusion,
    .gitignore entries for nytprof.out/nytprof/, and RED-then-GREEN test
    coverage in t/15-release-metadata.t.

4.46  2026-09-18

  - DD-947: extracted CollectorRunner.pm's command-execution cluster
    (run_command, await_windows_command, spawn_windows_command,
    record_command_pid, command_launcher_argv, command_pid_from_file,
    await_command_pid, forward_command_signal, terminate_command_process,
    exit_code_from_status) into a new sibling module,
    Developer::Dashboard::CommandRunner - first cut toward DD-641's
    1963-line oversized-module finding. Confirmed zero instance-state
    ($self->{...}) dependency in the 241-line cluster before moving it;
    every original call site kept as a one-line forwarder so no existing
    caller changed. CommandRunner.pm reaches 100.0 on all four
    Devel::Cover metrics. CollectorRunner.pm drops from 1963 to 1754
    lines.

4.45  2026-09-18

  - DD-942: closed Ask.pm's pre-existing branch/condition coverage gap
    (confirmed present before DD-938 touched the file: 99.2/89.4/77.7/100.0
    stmt/branch/cond/sub). Added 52 new regression subtests to t/48-ask.t,
    calling private helpers directly via $M->can() where run_ask's public
    interface cannot reach a specific internal branch. Result:
    100.0/98.5/96.8/100.0. Two residual lines are genuine Devel::Cover
    tooling limitations, filed separately as DD-944. Also caught and fixed
    a genuine regression mid-gate (t/168-permission-assertion-guards.t)
    from an incorrect uid-based SKIP guard, rewritten to match this
    project's established probe-then-restore permission-test pattern.

4.44  2026-09-18

  - DD-937: reviewed the Tira board-engine upgrade 5.131 -> 5.150 (19
    releases). Nothing new to declare (tira.policy.undeclared confirmed
    empty). Adopted two genuinely useful additions into how this project
    reads its own board: `d2 tira.police.explain --ref REF --rule RULE`
    (prints a rule's exact computation rather than only its final verdict,
    verified live against DD-667's own long-standing card-duration
    finding) and the `checklist-idle` "standing" label exemption for a
    container card meant to stay open indefinitely. Findings recorded in
    docs/tira-upgrade-review.md. Documentation-only, no product code
    changed.

4.43  2026-09-18

  - DD-943: fixed t/15-release-metadata.t's _perl_doc_paths() incorrectly
    scanning t/183-pax-cli-build-run-contract.t's leftover scratch fixtures
    under t/tmp-sow03/ (deliberately minimal-POD test inputs for the PAX
    build pipeline, never cleaned up at the end of that test's own run) -
    causing ~25 spurious POD-completeness failures whenever t/183 happened
    to run before t/15 in the same prove process. Added the same exclusion
    pattern already used elsewhere in the same file. Confirmed pre-existing
    (present in CI before DD-933 landed, not caused by it).

4.42  2026-09-17

  - DD-933: generalized DD-931's finding - CodeUnitCompiler.pm's
    dependency-discovery pass walks literal source text to decide a
    compiled entrypoint's compiled_packages closure, so a class referenced
    only inside a sub body the compiler special-cases (substituted
    wholesale with a runtime op) is invisible to it and never gets a
    __PAX_RUNTIME_LEGACY_NAMESPACE__ alias installed for some entrypoints.
    Confirmed the same gap hits JSON (~53 call sites) and SeedSync (4 call
    sites) in StandaloneRuntime.pm, called via the legacy-namespace alias
    from inside other special-cased ops. Fixed by requiring both classes
    directly at file-load time and calling them by their real package
    names, same convention already shipped for EnvAudit in DD-931. New
    test t/198 proves genuine RED-then-GREEN by invoking the real op
    dispatcher directly. Verified against a real Docker container with a
    clean-master baseline comparison: zero regressions.

4.41  2026-09-17

  - DD-934: fixed a compiled bin/dashboard entrypoint crash on ANY
    subcommand with a trailing argv token ("Can't use string as a HASH
    ref"). StandaloneRuntime.pm's _run_cli_router_unit called
    _prime_command_result_env with only ($cmd, @ARGV), silently dropping
    the $main_gate_results positional argument the function's own
    signature expects - the first argv token filled that slot instead of
    a hashref, crashing the moment any argument followed the command name.
    Fixed by passing an explicit {} at the call site. This blocked safely
    re-enabling self-exec, since almost every real invocation carries at
    least one argument. Verified RED-then-GREEN against a real compiled
    binary.

4.40  2026-09-17

  - DD-938: dashboard ask is now a Developer Dashboard expert by default.
    `dashboard ask --docs` prints a curated onboarding summary (DD-OOP-LAYERS,
    .env/docker-compose conventions, where a disposable helper script
    belongs) directly to stdout - never the full CLAUDE.md, never a new
    file. `dashboard source --files` lists every file installed under
    ~/perl5/{lib,bin} as a fallback deep-dive reference. A workspace's
    genuinely first-ever `dashboard ask <question>` call (empty transcript,
    not --no-memory) silently prepends the --docs context to that turn's
    prompt - no flag, no injected instruction line - and every later turn
    sees it already in replayed history. Also fixed a t/196 numeric-prefix
    collision with a pre-existing test file (renamed to t/197) and three
    POD gaps surfaced by the release-metadata gate.

4.39  2026-09-17

  - DD-935: fixed a pathological per-character substr() loop in
    CodeUnitCompiler.pm's own _extract_sub_body/_extract_sub_source
    (the compiler self-compiling its own ~13,000-line source was the
    dominant cost of a pax build - 4.5+ minutes for one file vs ~4
    minutes for the other 112 combined). Replaced with a \G-anchored
    regex scan: 1.4s -> 0.0005s per extraction call, verified
    repeatedly in isolation. Also dropped the launcher's cc flag from
    -O2 to -O0. A second, separate contributor to full-pipeline
    compile time was found during verification and filed as DD-939.

4.38  2026-09-17

  - DD-936: PaxCache::resolve() no longer spawns a new background pax
    build by default - owner-specified opt-in via DD_PAX=on, after
    observing pax build burn ~66% CPU for 90+ seconds per invocation
    merely from running dashboard/d2 init. Gate scoped to only the
    spawn-a-new-compile path (not cache-hit reporting), after an
    earlier blanket-gate version was found to break bin/d2's own
    self-exec feature and corrected the same day (Q-167).

4.37  2026-09-17

  - DD-931: require Developer::Dashboard::EnvAudit directly (instead of
    the never-populated __PAX_RUNTIME_LEGACY_NAMESPACE__ alias) in both
    of Pax's env-loading runtime ops, root-causing DD-930's mitigation.
    Verified against a real compiled binary: EnvAudit string-table
    references go from 0 (crash) to 51 (clean run). bin/dashboard's
    self-exec-on-cache-hit guard stays DISABLED - genuine end-to-end
    verification surfaced three further, independent defects (DD-932,
    DD-933, DD-934), each filed separately.

4.36  2026-09-17

  - DD-924: d2's self-compiled binary resolved $Bin to the pax cache
    directory it physically ran from, not the original bin/ source
    directory, breaking its own exec-into-dashboard fallback with
    "Can't open perl script". Fixed by baking the correct path into an
    env var on the genuinely-interpreted first run, before self-exec.

4.35  2026-09-17

  - DD-922: root-caused and fixed StandaloneRuntime's file-read corruption
    (a `local $/;` spanning too far, leaking into a later `eval` of the
    entrypoint's own compiled source via eval STRING's shared dynamic
    scope). Re-enabled bin/dashboard's self-exec-on-cache-hit - and DD-930
    (same day) found a second, independent defect in the same area and
    disabled it again; see that entry.
  - DD-923: fixed a UTF-8 double-encoding defect in CodeUnitCompiler.pm
    that corrupted literal non-ASCII characters (e.g. dashboard ps1's
    status icons) in PAX-compiled binaries.
  - DD-930: bin/dashboard's self-exec-on-cache-hit disabled again, hours
    after DD-922 re-enabled it - a second, independent PAX-compiler defect
    (EnvAudit->record failing inside the compiled binary's own legacy
    namespace) was found the moment the exec path was genuinely exercised
    against real project config for the first time since DD-905.

4.34  2026-09-17

  - DD-917: OpenFile.pm's _scope_match_rank compiled/resolved every pattern's
    regex unconditionally at the top of its per-pattern loop, even though two
    cheaper checks (exact basename match, prefix match) are tried first and
    often decide the score without ever needing it. Resolved lazily instead,
    only at the point of use.

4.33  2026-09-16

  - DD-918: lib/Developer/Dashboard/CLI/OpenFile.pm was 823 lines, exceeding
    this project's 500-line-per-module guideline. Extracted the Java-class
    resolution machinery (archive extraction, Maven Central search/download,
    the --online network gate) into OpenFileJavaSource.pm, the interactive
    multi-match chooser into OpenFileChooser.pm, and shared dedup helpers
    into OpenFileUtil.pm. All moved subs are re-exported into OpenFile.pm's
    own namespace so existing callers and t/98-cli-openfile-coverage.t need
    no behavior change.

4.32  2026-09-15

  - DD-868/DD-870/DD-878: FileRegistry::resolve_file, PathRegistry::resolve_dir,
    File::_resolve_file and Folder::_resolve_path all fell back to dispatching
    a caller-supplied alias name to ANY method the resolved object/class
    answered can() true for. Replaced with a compile-time allowlist of the
    intended no-arg path getters in each.
  - DD-874: Config.pm's api_registry merged the operator's own deepest-writable
    api.json layer BEFORE a skill's bundled key fragments, so an installed
    skill could silently replace or tombstone an operator-set API key. Skill
    fragments now merge first (lowest priority), the operator's own layer
    last (highest).
  - DD-877: added Developer::Dashboard::PaxCache, an MD5-keyed, non-blocking
    PAX compile cache - a cache miss runs interpreted immediately and
    triggers at most one detached background compile per source file, with
    an O_CREAT|O_EXCL lock guarding against overlapping invocations spawning
    duplicate compiles. Wired into dashboard ps1 as a proof of concept.
  - DD-871/872/875/876: researched PAX (a sibling adaptive Perl compiler)
    compiling this codebase - confirmed clean on glibc/Debian with a
    measured ~36x runtime speedup, found and reported two PAX bugs upstream
    (an objcopy long-option ambiguity on newer binutils, a hardcoded ELF
    output width ignoring the target architecture), and confirmed PAX has no
    macOS Mach-O build path at all (hardcoded ELF-only, zero platform
    branching) - a real, immediate blocker for any Mac user.
  - DD-866/867/869/873: corrected drifted architecture-overview POD in
    Developer::Dashboard.pm (an inactive UpdateManager module described as
    wired-in when it is intentional dormant groundwork; Data Query Commands
    listing 4 shipped formats instead of all 7; "prompt commands" phrasing
    inconsistent with the file's own established "ps1 commands" wording).

4.31  2026-09-11

  - DD-770: .claude/tools/host-ready's foreign-process detector matched on
    command-line TEXT, which is spoofable - a process can rewrite its own
    $0, and ps args/cmdline/comm all read the same rewritten buffer. Added a
    positive-only /proc/PID/exe veto to dd_foreign_now(): a candidate is
    excluded only when exe provably resolves to something other than
    perl/prove/cover; an unreadable exe (the ordinary case across a
    container's mount namespace) leaves it counted, never turning
    could-not-look into excluded.
  - DD-771: corrected a false premise found during this card's own research
    - the /tmp/dd-gate-host.lock shared with the Tira project is deliberate
    (TKT-857), not an accidental collision. Fixed the two genuine survivors:
    run-suite's refusal now names the holding project via its cwd instead of
    a bare pid, and a retry no longer silently truncates a failed attempt's
    log (preserved to $LOG.prev before truncation).
  - DD-740: extracted .claude/tools/board's DD-545 checkout-root resolution
    (git --git-common-dir) into a shared .claude/tools/checkout-root helper;
    board-pulse, next-action and tira-bridge-run now source it instead of
    each hardcoding the checkout's absolute path.
  - DD-785: extracted the duplicated paths-registry constructor guard from
    seven modules (Collector, Doctor, IndicatorStore, PageStore,
    Housekeeper, FileRegistry, Prompt) into
    Developer::Dashboard::PathsRegistryArg (require_paths_arg), mirroring
    DD-762's DirEntries.pm precedent.
  - DD-767: bound t/100 and t/103's empty-environ probe loops by wall-clock
    (Time::HiRes, 60s) instead of a fixed 3000-poll count - closing a gap
    DD-482 specified but never shipped. A poll count is not a stable time
    bound under Devel::Cover, where instrumentation overhead and host
    contention both change what a fixed count buys; raising the count (as
    shipped twice before) failed a third time under real load. Also fixes
    a zombie-blind "-e /proc/PID" liveness check with a state+cmdline
    diagnostic.

4.30  2026-09-05

  - DD-810: the switchboard now runs a main gate before resolving any command:
    every executable file directly under <layer>/hooks/ in the runtime layer
    stack runs once per invocation, deepest layer first and home last, with
    the full command argv, and each hook's RESULT/LAST_RESULT is recorded
    under the same keys the per-command chain writes. A hook that prints the
    marker [[STOP]] on stderr skips the remaining hooks and the command does
    not run; dashboard exits 1 with the hook's stderr visible. A layer stack
    with no hooks/ directory produces bit-for-bit today's output and an empty
    stderr. The hooks run with DEVELOPER_DASHBOARD_MAIN_GATE set so a nested
    dashboard or d2 does not re-fire the gate, and d2's re-exec of dashboard
    fires it exactly once. `dashboard which <cmd>` lists the main-gate hooks
    before the per-command ones, in run order.
  - DD-764: a stored session record whose expires_at was absent, empty or "0"
    was accepted as valid forever by SessionStore::from_cookie and skipped by
    sweep_expired, so the record was both immortal and uncollectable - while a
    malformed timestamp already failed closed, because _iso8601_to_epoch
    returns 0 for anything not matching its strict ISO-8601 pattern. Both
    truthiness guards are gone, so an absent expiry now compares the same way
    malformed input already did: the session is rejected and deleted on use,
    and the file is collected by the sweep. create() always writes expires_at,
    so reaching this needed a record written outside the product's own path -
    a hand-edit, a restore, or an older schema - which makes it a latent
    fail-open on a session bearer credential rather than an exploitable bug.
  - DD-737: a collectors config entry carrying only a name and an interval (no
    'command' and no 'code') was forked into a loop worker anyway. That worker
    died on its very first tick with "Collector <name> missing command or
    code" and on every tick after that, forever, without ever disabling
    itself, leaving an unreaped process behind each time it died on a host
    whose PID 1 does not reap orphans. Reproduced in the project's container
    image, where two `dashboard restart` cycles left three or more defunct
    "dashboard collector" entries that later restarts never cleared.
    start_loop now runs the same command/code validation _collector_source
    already performed per tick, before writing a pidfile or forking, so a
    misconfigured collector fails immediately with that error and spawns no
    process.
  - DD-753: RuntimeManager launched PowerShell by hardcoding the bare string
    'powershell' at four call sites, while CollectorRunner did the same job
    through a resolver that tries PATH under both spellings, then the
    SystemRoot install path, and names the missing executable when none
    resolves. On a Windows host where PowerShell is not on PATH, the collector
    said what was wrong and the dashboard runtime failed through system() into
    an empty capture, indistinguishable from a legitimately empty result. The
    resolver now lives once in ProcessSupervision and is used by both modules,
    so all four runtime call sites resolve PowerShell the same way, and each
    reports the missing executable in the shape its own contract requires - a
    status pair, a die, or an empty list. Verified on Linux only: no Windows
    guest was available, and the card records the platform verification as
    still owed.
  - DD-738: d2()'s AUTOLOAD handled only a single word and shelled out
    immediately, returning a plain string, so a second method call on it -
    d2()->collector->list - died as a bareword class lookup. A bareword method
    now starts a lazy proxy that accumulates dotted segments at any depth,
    mirroring the CLI's own dotted dispatch, and nothing executes until the
    chain is terminated with a call: d2()->foo->bar->() runs `dashboard
    foo.bar`. An un-terminated chain is inert in boolean, numeric and string
    context and stringifies to something obviously non-executing ("d2 proxy:
    foo.bar"), so a stray debug print cannot run a command; underscores in a
    segment are rewritten to hyphens, since a Perl method name cannot carry a
    hyphen and the hyphenated half of the CLI was otherwise unreachable
    through the chained form. INCOMPATIBLE CHANGE, and it reaches the
    single-word form too:
    d2->doctor now needs the trailing ->() to execute. d2->run(...), which
    takes its words as separate arguments and passes them through verbatim, is
    unaffected.
  - DD-616: Developer::Dashboard::File and Developer::Dashboard::Folder each
    carried their own copy of the computation that builds the configured-alias
    cache key, so the key and the runtime roots it is derived from could drift
    apart. Both now delegate to a single
    Developer::Dashboard::PathRegistry::alias_cache_key, on the registry that
    owns those roots. Of the five sub names the two modules share across 45
    lines, none was byte-identical, and the AUTOLOAD and
    configured-alias-loading pairs were deliberately left alone because they
    encode a real difference - Folder creates the directory it resolves and
    File must not create the file it resolves. Behaviour of the installed
    product is unchanged.
  - DD-669: nine subroutines shared a name between RuntimeManager and
    CollectorRunner while having different bodies, so the name asserted a
    sameness that did not hold and no copy-paste detector could see it. Four
    are now single implementations in ProcessSupervision -
    _helper_file_supports_internal_command, _same_pid_namespace
    (RuntimeManager's _current_pid_namespace_id became dead and was deleted),
    _close_inherited_fds (a union, since RuntimeManager's preserve_harness
    guard is a strict superset) and _dashboard_core_helper_path (its command
    argument is now required, the two copies having differed only in a default
    no caller in lib/ reached) - for a net -33 lines in lib/: 155 duplicated
    lines removed, 114 shared added. The remaining five keep their per-class
    names with the reason recorded beside each, including two that are
    interface contracts the shared helpers dispatch through and one that
    diverges because RuntimeManager's side is defective (tracked separately as
    DD-753). t/160 now derives the divergent set from source and compares it
    against the declared set, so both a new divergence and a
    resolved-but-still-declared one fail.
  - DD-656: tests that chmod a file unreadable and then assert the operation
    is denied cannot observe a denial when the running process is root, so a
    container run of the suite reported seven of them as product failures when
    the mismatch was in the environment, not the code. A new gate,
    t/168-permission-assertion-guards.t, scans every test file for that shape
    and was RED at 62 sites across 26 files; each site now probes whether a
    denial is actually observable - by attempting the operation after the
    chmod - and skips with a named reason when it is not, replacing earlier
    guards that tested process identity instead. A follow-up run on
    ubuntu:24.04, debian:stable-slim and the project image measured that -r
    still answers true for uid 0 where the open is denied, and that the
    `filetest` pragma named as the alternative is absent from stock perl on
    Ubuntu and Debian, so attempting the operation is the form kept.
    Test-suite work only; no change to shipped code.
  - DD-725: t/97-pathregistry-coverage.t deliberately writes to /dev/full to
    exercise a failed flush, but unlike t/103 and t/89 it carried no tolerance
    for the resulting "unable to close filehandle ... No space left on device"
    warning, so every full-suite run on a host with /dev/full leaked it to
    stderr. That block now carries a scoped local $SIG{__WARN__} matching
    exactly that text - both the close-filehandle phrase and the device clause
    - and rethrowing anything else. Measured before and after on the same
    tree: 164 tests and 0 failures both times, stderr lines 1 to 0, with the
    ENOSPC assertion present and not skipped in both runs. No library code
    changed: the warning is perl's own notice about an implicit close during
    die-unwind, not a defect in PathRegistry.
  - DD-732: t/35-js-fast-check.t asserted npm ci's exit code inside the
    `if ( !-d node_modules )` branch that performed the install, so the file
    planned 1..4 on the first run in a tree and 1..3 on every run afterwards -
    the same file at the same commit reporting a different count depending on
    a directory it had created itself. That made the whole suite's total a
    property of the working tree rather than of the commit, presenting as
    flakiness (17540 once and 17539 four times on one unchanged tree) and
    hiding any genuine loss of a single test in the noise. The install stays
    conditional, but the assertion now runs every time and checks the
    postcondition - that the dependency tree is present - which also catches
    an install that exits 0 and produces nothing. Verified from both states,
    with node_modules present and with it removed.
  - DD-748: in a container without Devel::Cover,
    t/148-coverage-gate-entrypoint.t failed 29 of 50 assertions and
    t/151-coverage-gate-launch-boundary.t died after 3 of a planned 9, so a
    missing optional tool was reported as a test failure rather than an absent
    prerequisite. Both files now detect Devel::Cover's absence before any plan
    is declared and skip_all with a reason naming the module, following
    t/138-coverage-exec-truncation.t which already did this. A new
    t/170-optional-tooling-skips.t masks the module with an @INC hook in a
    child process and asserts all three specs skip, name the module, exit 0
    and abandon no plan - while confirming t/148 and t/151 still run all 59
    assertions when Devel::Cover is present, so a guard that always skipped
    would be caught.
  - DD-766: the behavioural tarball-exclusion block in
    t/15-release-metadata.t compiled all 17 exclude_match patterns out of
    dist.ini but applied them to a hardcoded list of 8 sample paths, so 9
    patterns were compiled, exercised by nothing, and passed silently. Nothing
    leaked, but a deleted dist.ini line would not have been noticed. Sample
    paths were added for those nine, and a loop now asserts that every
    compiled exclusion is matched by at least one sample, so an exclude_match
    added to dist.ini without a sample fails instead of quietly reducing what
    the block covers. Added alone, the loop failed for exactly nine patterns;
    with the samples it reports 17 of 17 exercised, and the pre-existing
    must_be_shipped assertions are unchanged at 7. Falsified by adding a
    throwaway exclusion, confirming it went red, and removing it again.
  - DD-741: CodeQL refuses to run unless its init, autobuild and analyze steps
    use the same version, but Dependabot opens one pull request per action
    path - so each bump moved a single step, stranded the other two, and every
    pull request failed the required "analyze (javascript-typescript)" check
    with "Loaded a configuration file for version X, but running version Y".
    It happened three times (4.37.6 to 4.37.7, 4.37.7 to 4.37.8 and 4.37.8 to
    4.37.9, as PRs 48/49/50) before being fixed at the cause. A Dependabot
    group scoped to "github/codeql-action*" now makes the family arrive as a
    single pull request that can pass; the pattern is deliberately
    family-scoped rather than a blanket wildcard, which would batch every
    action update so one failure blocked the rest.
    t/171-dependabot-grouping.t guards the block and was falsified in three
    directions - removing the block, narrowing it to one path, and widening it
    to "*" - before being trusted.
  - DD-655: a recorded gate verdict stored only the HEAD tree hash, so
    gate-status could tell that a verdict described an older commit but not
    that it described different uncommitted work - measured on DD-652, where
    the sandbox's HEAD tree had not moved all night while all six files the
    card changed hashed to something that tree does not describe. coverage-run
    now records a working-state fingerprint beside the tree hash - git status
    --porcelain --untracked-files=no plus git diff HEAD for uncommitted
    tracked work, and every file under the operator tools directory by
    relative path and content for the git-ignored tooling - and gate-status
    compares it with the same expression, so "a different commit" and "the
    same commit with different uncommitted work" are told apart, while a
    fingerprint that cannot be computed reports unknown rather than clean. The
    rejected alternatives are recorded with their reasons: git stash create
    captures tracked modifications only, and hashing the whole working tree
    invalidates on every unrelated edit. A follow-up closed a blind spot in
    the walk itself - a filename containing a newline split into two unreadable
    non-paths, letting its content change without moving the digest.
  - DD-696: host exclusivity between gate runs was enforced on only one side:
    coverage-run serialises by delegating to script/coverage-gate's flock,
    while run-suite exec'd prove directly and took no lock at all - measured
    by starting two full suites twelve seconds apart, both of which ran and
    the second printed no refusal. run-suite now takes an exclusive lock
    immediately before launching prove, refuses naming the holder rather than
    waiting, and scopes the lock to the prove run only; script/coverage-gate
    was deliberately left unchanged here, with that half tracked separately as
    DD-734.
  - DD-734: the suite runner has held a host-wide lock since DD-696, while
    script/coverage-gate locked only a file named after its coverage database
    (DD-526) - two different files, so neither tool could ever refuse the
    other, and a suite starting mid-coverage silently invalidated the coverage
    verdict. The gate now also takes the same host lock the suite runner uses
    (DD_SUITE_LOCK, default /tmp/dd-gate-host.lock), appended to the database
    lock rather than replacing it, so two gates given different --database
    paths still do not refuse each other. Because the suite itself invokes the
    gate in nine test files, the host lock is skipped when HARNESS_ACTIVE is
    set, so the gate cannot be refused by the suite that is running it; a
    follow-up fixed the lock test, whose real-run cases had inherited
    HARNESS_ACTIVE from prove and were exercising the guard instead of the
    lock. t/146-coverage-gate-lock.t grew from 9 tests to 15.
  - DD-744: script/coverage-gate derived its repository from its own file
    location and chdir'd there, so every relative path after that - the
    cover_db database and the t/ test directory - resolved in the script's own
    tree; invoking one checkout's copy from a different working directory ran
    that first tree's suite and deleted and rebuilt its coverage database,
    while the run succeeded and named neither tree. The gate now refuses when
    both the working directory and its own repository are git checkouts and
    they differ, naming both absolute paths, and it reports the resolved
    absolute database instead of the configured relative name that was
    byte-identical whichever tree it landed in. The refusal condition is
    deliberately narrow so the existing specs' temporary non-checkout
    repositories still run, and the two roots are compared case-insensitively
    where the filesystem is case-tolerant so the guard cannot falsely refuse
    in its own checkout on Windows or a case-tolerant macOS volume. New spec
    t/169-gate-target-resolution.t covers the refusal, the proceed case, and
    the absolute database reporting.
  - DD-729: host-readiness waiting was rewritten from scratch in each session
    that needed it, and the copies disagreed about what "foreign" means: the
    in-run sampler counted only a Devel::Cover process while the ad-hoc
    pre-run waits also matched a foreign workspace path, so a run could be
    launched as clear by one definition and then classified against the other.
    There is now a single readiness waiter with a spec, and both mechanisms
    obtain one definition rather than each carrying a copy. Two recorded
    claims were corrected by measurement: the path-based definition returns 19
    processes on this host against 2 for the harness-based one - 18 of them a
    policy bridge, a `tail -F` and another project's watcher - so a wait using
    it would never launch, and every contention figure recorded before the
    definition change now carries a caveat that it cannot be pooled with
    anything measured after it.
  - DD-746: the suite runner's in-run sampler counted only Devel::Cover
    processes, so a competing plain prove was never counted - measured side by
    side, a /proc scan saw one foreign prove at the same moment the runner
    wrote FOREIGN_PEAK=0, a marker that reads as "the host was empty".
    Widening the pattern under DD-729 reversed the defect rather than fixing
    it: the verdict still called what it saw "a foreign coverage run" that
    "held the host", while the grep now matched any foreign test or coverage
    process and being seen in a sample is not evidence of holding anything.
    The CONTENDED and SLOWED lines now read "a foreign test or coverage
    process was seen in N of M sampled windows (peak P)", and the marker line
    carries FOREIGN_PATTERN so every recorded figure states the definition
    that produced it - without which the eleven figures gathered under the old
    narrow pattern cannot be pooled with new ones.
  - DD-727: the gate-status reporter decided whether a suite verdict stood
    from SUITE_EXIT and GATE_EXIT alone, and read none of the contention
    markers run-suite writes into the same log - a grep for CONTENDED, SLOWED,
    FOREIGN_PEAK, FOREIGN_SAMPLES and WINDOW_SAMPLES across the whole tool
    returned zero matches - so a log whose own line read "this verdict
    competed for the host and does not stand" was reported as a finished gate,
    in the reassuring direction. Corroborated four times on the card (DD-682
    at 49 of 55 contended windows, DD-733 at 50 of 56, and both of DD-744's
    runs at 34 of 44 and 35 of 45), all of which passed and all of which were
    discarded by hand. The reporter now carries run-suite's own classification
    rather than re-deriving the threshold in a second place: a CONTENDED line
    overrides SUITE_EXIT and the verdict is reported as not standing, a SLOWED
    line is recorded without invalidating, and the three states - passed, did
    not stand, could not look - stay distinct, so broken code remains
    distinguishable from inadmissible evidence.
  - DD-750: the coverage tool's host-contention sampler excluded only its own
    process group, while the gate it launches runs under setsid in a different
    one - and a coverage run's children are Devel::Cover processes by
    construction, so the count was inflated in every window of every real run
    and no coverage verdict on this host could come out uncontended. Measured
    with a control pair at identical ambient load, a stand-in gate spawning no
    Devel::Cover-shaped child gave FOREIGN_PEAK=6 and one spawning a single
    child gave 7; after the fix both read 2. Rather than patch it in place,
    the private copy of the predicate was replaced by a call to the shared
    definition, which also supplies four protections that copy had never
    received: the process-group exclusion list, the quoted-pattern grep, the
    pattern-compile check and the empty-process-table check.
  - DD-761: the Docker image the platform gate runs against was built FROM
    itself and tagged back to the same name, so rebuilds layered on the
    previous build instead of starting clean, and a checked-in tarball that
    nothing regenerated left the image three weeks behind master.
    String::Compare::ConstantTime - declared as a runtime dependency in
    cpanfile, dist.ini and Makefile.PL, and loaded by Auth.pm at compile time
    - was absent from the image, so every path through Auth.pm died there and
    `dashboard encode`/`decode` exited 2 for every input; the failure first
    surfaced as a plausible-looking empty-string property-test
    counterexample, and only a control run (encode("hello") failing
    identically) showed the module was never loading at all. The image build
    now pins a real base, regenerates its tarball from master, and fails at
    build time when any dependency cpanfile declares cannot load. No shipped
    code changed: the dependency was always declared correctly, and the fix
    lives in the operator's local image-build directory, which the
    distribution excludes.
  - DD-682: the scheduled repository hunters fingerprinted a finding's whole
    item set, so any change of membership refiled every member: 28 filed cards
    carried only 11 distinct findings, 17 of them redundant refs, with
    "modules over 1500 lines" filed seven times in three days. Card titles
    also carried the item count, so one finding appeared as "(6)" and "(7)"
    and no title match could tell they were the same. Findings are now
    fingerprinted per item and only unreported items are filed, titles carry
    no count, and a new --report-all mode empties the ledger for the run so a
    card's recorded reproduce command prints the finding instead of "0 card(s)
    raised". Operator-local board automation only; no shipped code was
    touched.
  - DD-733: the repository's internal "what next" helper encoded a picture of
    the board that had drifted in four measured ways: it offered a card the
    owner had explicitly deferred through two answered-and-marked questions,
    never proposed work from the buglist, new-enhancements or backlog columns,
    was blind to the researching, analysing and documentation columns so its
    own "never start something new while something is claimed" rule could not
    fire, and asserted every backlog card was parked on a condition when 24 of
    27 carried none. It now consults per-card policy declines when judging
    whether a candidate is workable, covers those columns in both its
    in-flight and candidate loops, and drops the false parked claim; its
    accompanying spec grew from 15 assertions to 35.
  - DD-742: the operator next-action guard listed four columns as in-flight
    (in-progress, researching, analysing, documentation) while the board
    declares eleven working columns, so a card at a later gate did not
    suppress a new recommendation: observed 2026-09-02 with a card in
    unit-test, the guard answered "DD-659 is waiting to be started. Claim it
    and work it". It now asks the board, where required_actions > 0 selects
    exactly the eleven working columns, so a twelfth column needs no code
    change, and an empty result fails into the existing could-not-look path
    rather than reporting that nothing is in flight.
  - DD-747: the next-action helper walked the pick-next column order but
    applied no ordering within a column: its candidate listing returns refs in
    ascending order, so the loop always took the oldest workable card.
    Measured on 2026-09-02, it named DD-637 (priority 2) as the next thing to
    do while DD-744 (priority 4) sat unworked in the same column. The tool now
    fetches ref and priority once per run and sorts each column's candidates
    by descending priority before picking.
  - DD-756: the hourly policy sweep compares the board's live policy set
    against the project's policy manifest, and the two records had drifted
    across the board upgrades: 61 policies were declared on the board while
    the manifest held 32 declared and 2 declined, with ten rules
    (card-changed-by-owner, agent-still, card-agentless, task-unlinked,
    task-changed, job-due, task-card-mismatch, monitor-dead, monitor-output,
    monitor-silent) present on the board and absent from the manifest, so the
    sweep exited 1 every hour asking for judgements that had already been
    made. Each of the ten is now judged and recorded in the manifest with its
    reason, and the sweep returns to exit 0. The manifest and the sweep are
    operator-local tooling; nothing in the installed distribution changes.
  - DD-679: nothing walked the working copies on the development host, so work
    sitting on disk that no card knew about went unnoticed: three checkouts
    were found holding content not on master, two of them belonging to cards
    already in terminal columns. A new developer-documentation page records
    the measured layout - 169 registered worktrees split 32/100/36 across
    three roots, one of them inside the main checkout - and the two identity
    tests a checkout must satisfy before anything reports on it, since a clone
    of this repository fails a git-common-dir check while a
    correctly-registered worktree of an unrelated project can sit in our own
    superseded sandbox root. It also records why "this directory differs from
    master" is the wrong question: scored by hand against three cases it gave
    one true finding and two false alarms, because a stale tree routinely
    holds a superseded draft, so the sweep asks whether the commit carrying
    the card's ref landed instead.
  - DD-659: the development host-exclusivity rule was written down but not
    scoped, and a hand-written per-session wrapper had held a host lock across
    an entire gate chain - 2529 seconds, with its prove child long since
    exited, while another session's twelve-minute suite waited over forty
    minutes. A new development-process page records the convention that such a
    lock is acquired immediately before the work and released when that
    process exits, notes that run-suite and coverage-gate already scope this
    way (per run and per coverage database respectively), and states the two
    limits run-scope does not remove: a waiter cannot tell a deliberate hold
    from a hang, and it does not prevent starvation between waiters using
    different strategies. No code changed - the offending wrapper was
    per-session and no longer exists.
  - DD-757: two places in the repository explained that docs/ never reaches
    the release tarball because dist.ini's [GatherDir] gathers under doc/
    (singular) and never looks at docs/ (plural). That reason is false:
    [GatherDir] declares no root, so it gathers the whole distribution root,
    and docs/ is kept out by one explicit line, dist.ini's
    `exclude_match = ^docs/`, which neither artifact mentioned. The conclusion
    itself was never wrong and had been checked by building the distribution
    before and after and finding zero docs/ entries both times; only the
    attribution was wrong, and a reader trusting it could delete that exclude
    line as redundant, after which docs/ would ship. Both now name the line
    and state plainly that removing it ships docs/, cross-referencing the
    earlier case (DD-401) where a git-ignored file still reached a local 4.22
    tarball because dzil gathers from disk rather than git.
  - DD-776: since Tira 5.77 the board tool raises its own card whenever it is
    upgraded, but no written procedure existed for working one, so each review
    invented its own checklist. An internal documentation page now records
    what that ticket actually asks - that nothing is left undeclared, and that
    no already-declared policy would be refused by a validation the upgrade
    added - and what it does not ask, since there is no per-version manifest
    to diff against. The 5.56 to 5.77 review it was written from found
    `tira.policy.undeclared` empty and none of this project's 61 declared
    policies setting --age on the eighteen rules 5.61 gave forbids => ['age'],
    so nothing needed correcting.

4.29  2026-08-18

  - DD-585/589/590/591/592/593: six query functions across CollectorRunner.pm,
    IndicatorStore.pm, RuntimeManager.pm, ActionRunner.pm, SkillManager.pm
    (three functions) and PageRuntime.pm each shelled out (via system(),
    Capture::Tiny, or a waitpid reap) and left Perl's global $? holding their
    own last subprocess's exit status after returning, without ever
    localizing $? at sub entry. A caller reading bare $? afterward for an
    unrelated reason - the shape that first surfaced this in DD-585, an END
    block in a test file - silently inherits the wrong value. Found by a
    systematic sweep of lib/ once the first instance was fixed, and
    independently by the autonomous hourly bug-hunt round, which found and
    fixed DD-590 and DD-593 on its own (both resumed here after the round's
    own session ended mid-verification, with nothing lost either time - its
    park mechanism correctly left the tree untouched while a suite was live).
    Fixed uniformly with `local $?;` at each sub's entry. One subtlety
    verified rather than assumed: SkillManager's three functions never read
    $? explicitly, but Capture::Tiny returns system()'s own return value
    automatically while system() ALSO mutates the global $? as an
    unavoidable side effect - confirmed with a standalone repro before
    writing any fix. A fourth possible instance (ActionRunner's own sibling
    query pattern) was checked and is genuinely absent.
  - DD-594: dd-round-park.sh's automatic end-of-round park comment defaults
    to the shared, retired 'DD Bot' identity (DD-527 retired it project-wide)
    because nothing in the automation chain ever sets DD_TIRA_PARK_AUTHOR.
    Found by the autonomous round tracing the root cause of a finding that
    had been recurring on every card it touched.
  - DD-586: t-dd-round-park.sh's specs-guard timeout (600s) was too tight for
    a spec that deliberately spawns real long-lived background processes to
    model a genuinely-alive concurrent round; raised to 1200s.
  - DD-587: the hourly bug-hunt and 10-minute blocked-resolver systemd
    timers had been silently disabled for 9 days. Re-enabled and verified
    both correctly defer to an active interactive session before doing real
    work.
  - DD-562: master's coverage gate read 100.0 locally but 99.9 on CI.
  - DD-567: a perl-interpreter CVE advisory blocked the CI step immediately
    before tests and coverage, on 5 consecutive pushes.
  - DD-568: an early CI step failing silently skipped the test and coverage
    steps that followed it; those steps now run with if: always().
  - DD-572: the pre-push security grep matched 'brewfile' against the
    retired-system marker pattern 'ewf', making a required gate return
    permanent noise.
  - DD-573: 13 operator-tool specs existed under dd-tg with nothing ever
    running them, so a broken spec could never fail loudly.
  - DD-574: the audit gate could fail without ever saying which check failed
    it, a defect DD-567's own fix left behind.
  - DD-581: nothing recorded a coverage verdict anywhere the board's own gate
    indicator could read, so it could never show green even on a clean pass.
  - DD-582: gate-status reported RUNNING for a log file it had no way to know
    was actually still being written.
  - DD-583: the Telegram ack watcher answered a direct question about its own
    silence with more silence.
  - DD-584: two dd-tg operator specs had been failing with nobody told.

4.28  2026-08-15

  - The coverage gate's own regression test no longer asserts that this machine's
    library tree exists everywhere. It runs the gate in an environment stripped of
    PERL5LIB and expected it to resolve its dependencies anyway, which is true only
    where those dependencies sit in the one directory the gate repairs. Where they
    do not, the gate correctly reports a missing module and the test called that a
    failure. It now skips those two cases with the reason printed, keeps every
    structural assertion, and still goes red if the repair is removed.
  - 4.27 was tagged but never published: this test failed in the release workflow,
    and a job that fails early makes every later step skip, so the tag exists with
    no release behind it. Nothing in 4.27 is lost - it is all here.

4.27  2026-08-15

  - Browser smoke tests no longer hang a machine whose browser cannot reach its
    own loopback address. A coverage run sat on one such fetch for one day and
    twenty hours, holding a web server, a starman master and worker and a whole
    Chrome tree, while producing no failure, no exit status and no last line - a
    stalled run looks exactly like a slow one. Every external command those tests
    run is now bounded, and the bound kills the command's whole process group
    rather than the single process it can see, because the wedge left seven
    processes alive and killing the visible one would have reaped one of them.
  - Those tests now ask, before doing anything else, whether the browser can
    fetch a page that is definitely being served. If it cannot, they skip and
    print why - the probe URL, what the browser did, and that the product is not
    implicated. The probe serves that page over plain HTTP from a nine-line
    socket server rather than from the product, which is what makes it a
    diagnosis instead of a second copy of the test: if plain HTTP works and the
    real fetch then fails, that is a genuine failure and is not skipped.
  - The workspace command hands the terminal to tmux with exec instead of
    capturing the attach. It used to funnel a full-screen interactive program
    through a capture buffer nobody reads until it exits, and leave a perl
    process parked as its parent for the life of the session - possibly days.
  - Collectors: a lost pidfile no longer multiplies supervisors. Start, stop and
    status all treated that file as the authority on what was running, so once it
    was gone every start forked another loop that nothing could see, stop or
    count; twenty-seven were alive at once for a collector declared singleton,
    each firing every fifteen minutes. They now ask the process table, adopt the
    loop already running, and repair the record.
  - A collector stopped by a signal now records which signal stopped it and which
    process received it. A 'stopped' state with no pidfile is otherwise identical
    whether the loop shut down in an orderly way, was stopped by a watchdog, or
    was killed by something else on the machine - and that ambiguity sent four
    separate investigations down the wrong path.
  - The coverage gate carries its own library path. Launched from cron or a
    transient systemd unit - environments that read no shell profile - it died
    naming a dependency that is declared in three manifests and installed. It
    resolves that tree itself now, and still fails loudly when a dependency is
    genuinely absent, because it adds a directory only when the directory exists
    and runs no external command.
  - The collector start path no longer re-checks a pid it has already guaranteed.
    When the recorded pid is missing or empty it is replaced by a search of the
    process table, which returns a real pid or nothing, so the later test for a
    usable pid could never fail. The guarantee is now stated once where it holds
    instead of being re-checked where it cannot, which is one fewer piece of code
    that looks like it protects something.

4.26  2026-08-10

  - Packaging: stopped the release tarball carrying two things it never should
    have. logs/ shipped the board sweep's own runtime output - findings about
    this project's cards - to anyone who installed the product, because dzil
    gathers from disk and cron recreates that directory hourly. SECURITY_CHECKS.md
    shipped the internal security-audit protocol, which belongs with the operator
    files that are deliberately never released. Both are excluded now and t/15
    fails if either exclusion is removed.
  - Board tooling: the FT99 sweep decides from the cards themselves instead of a
    retired markdown page, so a card parked on the owner with nothing left to
    answer is reported rather than silently accepted, and a card holding an
    unanswered question anywhere else is reported too. It also reports a card
    claimed in a working column that has stopped moving, and one left untouched in
    the backlog.
  - Coverage gate: refuses to grade a database written by a different
    Devel::Cover install, and says which two disagree, instead of exiting with an
    unexplained status.

4.25  2026-08-09

  - CI: restored a pipeline that had certified nothing for ten days. Three
    GitHub Action pins sat on the node20 line while their version comments
    claimed node24, so every run died at Setup Perl and the suite, the coverage
    gate and both CVE audits never executed (DD-449). The pin comments are now
    resolved against the upstream tag rather than trusted (DD-449), the
    environment export no longer swallows its own failure inside a command
    substitution (DD-483), and two suite files no longer assume tools and
    libraries CI installs elsewhere (DD-485).
  - Security: helper session ids are drawn from the operating system CSPRNG
    instead of a SHA over the pid, the wall-clock second and built-in rand -
    the construction CVE-2026-13577 describes in Dancer2, reimplemented here in
    the code guarding the web interface. Crypt::URandom is now a declared
    dependency floored at 0.55, because CVE-2026-2474 affects earlier releases
    (DD-452).
  - Security: dispositioned CVE-2026-13577 for Dancer2, which has no fixed
    release, behind a guard that fails the audit if any production code ever
    activates a Dancer2 session (DD-451).
  - Security: closed a Zip Slip in dashboard open-file, where a sources jar
    could write attacker-controlled paths anywhere the user can write (DD-498).
  - Security: corrected the CVE gate's subject so it judges the product's
    declared chain rather than the host interpreter (DD-499).
  - Fix: a collector's loop state is written before its pidfile, so a
    concurrent running_loops can no longer delete the pidfile of a healthy loop
    and leave it unstoppable by name while the supervisor forks a duplicate
    (DD-488).
  - Fix: Housekeeper's temp-file cleanup lists its directory instead of
    globbing a pattern built from the path, so a directory whose path contains
    a space stops hiding every real temp file and unlinking unrelated ones
    (DD-502).
  - Test reliability: the coverage gate, the empty-environ probe and several
    collector lifecycle fixtures no longer fail purely because the host is
    CPU-starved, which is the shape of every CI runner (DD-482, DD-486,
    DD-489). Every timeout changed here is backed by a measurement rather than
    by the fact that a larger number passes.

4.24  2026-08-08

  - Security: closed the open-redirect class in the login redirect sanitizer
    and on both HTTPS-enforcement Host-header redirect paths (DD-415, DD-419),
    rather than only the single reported byte.
  - Security: closed three path-traversal classes - saved-page writes outside
    the dashboards root (DD-395), parent-directory traversal in every
    skill-namespaced web route with static-file containment asserted (DD-416),
    and skill installs whose repo name escapes the skills root (DD-426).
  - Security: every page-derived value the web layer places inside an HTML
    attribute is now escaped for attribute context (DD-421), and saved
    bookmark link segments are percent-encoded for URL context (DD-423).
  - Security: state-changing requests are rejected when Origin or Referer is
    foreign, on every web tier (DD-422).
  - Security: the transient-token denial now depends on the token alone, so a
    file value riding along can no longer bypass the 403 (DD-425).
  - Security: release tarballs no longer carry dogfood QA evidence, ticket
    worktrees (DD-414), or the Hermes runtime state root and its credentials
    (DD-432).
  - Feature: first-class self-upgrade - dashboard upgrade and d2 upgrade run
    the canonical platform installer (DD-386).
  - Feature: a real /favicon.ico is served instead of a 404 on every page
    load (DD-407).
  - Collectors: a timed-out command's full process subtree is terminated
    (DD-388); blocked Windows command collectors are interrupted at their
    timeout (DD-389); the atomic writer uses a per-process pending name so
    concurrent workers stop clobbering each other (DD-418); log append and
    rotation share a per-log lock (DD-399) and rotation cuts on entry
    boundaries so a combined lines+days rule cannot orphan an entry body
    (DD-427); collectors keep running when the updates directory is absent
    (DD-398).
  - Runtime: saved-Ajax disconnect cleanup terminates descendant processes
    (DD-396) and gives them a real elapsed SIGTERM grace window (DD-428);
    runtime state readers survive a transient empty state file (DD-411) and
    the web lifecycle no longer acts on incomplete runtime state (DD-412).
  - Windows: the SSL certificate home resolves through PathRegistry so the
    HTTPS server starts (DD-417); every passwd lookup is guarded so
    non-interactive sessions resolve a user and a home (DD-413); PERL5LIB
    generation is cross-platform (DD-397); PowerShell startup caches are
    anchored to the home layer and no longer clobber live PATH state
    (DD-390).
  - Web UI: every generated HTML document carries a lang attribute (DD-405);
    logout and an authorized /login route by trust tier (DD-408); page-editor
    textareas carry programmatic accessible names (DD-409); rendered page
    chrome no longer overflows 320px phone viewports (DD-410).
  - Performance: PathRegistry working-directory lookups no longer fork
    /bin/pwd repeatedly (DD-393).
  - Gates: the POD syntax gate reads the source tree instead of a stale copy
    and the three reds it was hiding are fixed (DD-429); the coverage gate
    grades lib/ instead of the stale build tree (DD-431) and a failed exec in
    the harness process can no longer blind it (DD-428); the Windows
    collector-timeout E2E is proven on a real QEMU Windows guest and made
    repeatable (DD-404).
  - Metadata: MakeMaker license metadata aligned with MIT (DD-400).

4.23  2026-07-29

  - Coverage: every module under lib/ now reaches 100.0 on all four
    Devel::Cover metrics - statement, subroutine, branch, AND condition -
    driven by the new per-module hermetic coverage suites (t/59 through
    t/106) with genuinely-unreachable outcomes annotated inline and dead
    guard operands removed where redundancy caused unreachable branches.
  - CI: all three workflows (test, release-github, release-cpan) enforce the
    all-metric standard through script/check-all-metric-coverage, which
    fails closed on any metric below 100.0 or any malformed report
    (t/107-all-metric-coverage-gate.t).
  - Security: helper host-trust no longer accepts arbitrary Host headers
    that merely resolve to loopback, closing a DNS-rebinding vector; exact
    loopback literals and configured alias hosts remain trusted.
  - Test isolation: the fresh-runtime API assertions in
    t/15-cli-module-coverage.t now chdir into their isolated HOME so a
    dev-runtime layer in the working checkout cannot capture their writes.
  - Installability: the dependency hardening initially raised the Perl floor
    to 5.44, which cpanm rejects on every mainstream perl (perl:latest is
    5.42); the floor is restored to 5.38 (the code's real requirement) while
    CI continues to run on Perl 5.44 and the dependency version pins keep
    the security gate.

4.22  2026-07-15
    - the housekeeper now sweeps expired helper-session files each run (wiring the
      SessionStore expired-session sweep added in 4.21 into the periodic cleanup),
      so stale session records no longer accumulate unbounded between logins

4.21  2026-07-15
    - batch of fixes from the automated bug-hunt (round 1), each test-covered:
    - config: the merged config now always dedupes the `collectors` array by
      name and `providers` by id, even when only one layer defines them (a
      single layer repeating a collector name previously leaked duplicates);
      config.json / api.json are now written atomically (temp file + checked
      close + rename) instead of truncated in place
    - security: the encoded-action transient path no longer trusts a
      payload-supplied `source`/trusted flag, closing a forged-token arbitrary
      command execution; the session id from the cookie is charset-sanitized
      before path use, closing a session-file path traversal; the session cookie
      gains `Secure` over HTTPS; helper passwords are now stretched with
      PBKDF2-HMAC-SHA256 while still verifying existing hashes
    - collectors: `timeout_ms` now round-trips correctly (no more x1000 blow-up
      on Windows worker restart); a `* * * * *` cron collector no longer bypasses
      per-minute dedup; signal-killed collector/action commands are recorded as
      failures instead of exit 0; background actions guard setsid on Windows
    - state writes: Collector and IndicatorStore atomic-write helpers now check
      close() and no longer unlink before rename; a git error is no longer
      reported as a dirty tree
    - added SessionStore::sweep_expired for expired-session reclaim (wiring it
      into the housekeeper, plus the DNS-rebinding host-trust tightening and the
      timed-out-command subtree kill, are deferred as follow-ups)

4.20  2026-07-15
    - security: in `dashboard serve --ssl` the public front-proxy relays raw TLS
      to the internal loopback backend, so every backend connection arrived from
      127.0.0.1; combined with a `Host: 127.0.0.1` header a remote HTTPS client
      was classified as trusted loopback admin and got unauthenticated admin.
      The internal SSL backend now marks itself proxied and never grants the
      loopback-admin shortcut, so remote HTTPS clients must authenticate.

4.19  2026-07-15
    - ship `d2` as a real, installed command instead of only a shell-bootstrap
      alias: `bin/d2` is a first-class entrypoint that re-execs its sibling
      `dashboard` with the same arguments, so `d2 <command>` behaves exactly like
      `dashboard <command>` and now works in scripts, cron jobs, and fresh shells
      that never sourced the dashboard shell integration
    - the shell bootstrap keeps completion wiring for `d2`, and the generated
      shell shortcut remains as a convenience where the bootstrap is loaded

4.18  2026-07-15
    - add `dashboard ask <question>`: a uniform AI-assistant command that answers
      from the Anthropic API when an `ANTHROPIC_API_KEY` (or the `claude` config
      domain) is set, and otherwise falls back to the local `claude` CLI
    - support pluggable backends `--claude` (default), `--codex`, `--copilot`,
      and `--gemini`; the chosen backend becomes sticky per workspace, and each
      shells out non-interactively in a forced read-only mode
    - remember the conversation per workspace (keyed by WORKSPACE_REF or the
      active project root) so follow-up questions carry prior turns; `--new`
      starts fresh and `--no-memory` skips the transcript for one turn
    - inline `--file` text attachments into the prompt and pass image attachments
      natively (Anthropic image blocks, codex `-i`, copilot `--attachment`); the
      transcript is stored owner-only under the runtime state root

4.17  2026-07-15
    - make `dashboard collector stop <name>` actually stop the collector: it now
      routes through the supervisor-aware stop path so the collector watchdog is
      deregistered and cannot immediately respawn the loop (previously it called
      the low-level loop stop directly, so the supervisor restarted the collector
      and its status snapped back to running)
    - stop the collector supervisor from creating a DUPLICATE loop when it races
      a freshly started loop before that loop has set its process title, by
      recognizing an already-running loop from its recorded state as well as by
      process identity
    - reset the consumer-facing collector `status.json` (running / active_runs)
      when a loop is stopped, so prompt, web, and CLI status readers reflect the
      stop immediately
    - reliably terminate long-running collector command subtrees on stop: send
      the worker process-group SIGKILL unconditionally so a SIGTERM-ignoring
      child is still reaped, kill the loop first and scale the TERM grace to the
      worker count so the loop is not KILLed mid-cleanup, persist active worker
      pids on spawn, and sweep orphaned workers left behind by a crashed loop
    - recognize managed collector loops from recorded state on hosts without
      `/proc` or `ps` (Windows), so stop can still terminate them
4.16  2026-06-11
    - teach the built-in `dashboard workspace` command a `-c` flag, accepted
      before or after the workspace name, that changes into the registered
      dashboard path for that name before planning the session, so
      `dashboard workspace -c foobar` behaves like `cdr foobar` followed by
      `dashboard workspace foobar` and the tmux session plus its layered
      `.env` refresh start from the registered project directory
    - fail `-c` explicitly when the workspace name is not a registered
      dashboard path, when the registered target is not a directory, or when
      the directory cannot be entered, instead of silently starting the
      workspace from the wrong directory
4.15  2026-06-11
    - keep the `install.sh` post-install activation runner on the same shell
      dialect as the bootstrap target, so blank hosts without `SHELL` exported
      (fresh Docker containers, `curl ... | sh` runs) no longer source the
      bash-flavoured activation files through plain `sh` and die on the first
      bash-only construct
4.14  2026-06-11
    - align every module under `lib/` back onto the single repository release
      version after the `4.13` bump only advanced `Developer::Dashboard`,
      leaving the other 53 packaged modules still declaring `4.12`
    - add a release-metadata gate that walks every `lib/**/*.pm` and asserts
      its `$VERSION` matches the `dist.ini` release version, so a partial
      version bump can never ship again
    - validate IPv4 loopback octets strictly in `Auth::_ip_is_loopback`, so
      malformed literals such as `127.0.0.999` are no longer classified as
      loopback addresses
    - restore the `4.12` entries in `Changes` and `FIXED_BUGS.md` that the
      `4.13` update overwrote instead of prepending
4.13  2026-06-11
    - harden the GitHub-hosted `Devel::Cover` gate again by exercising the
      installed-dist `File::ShareDir` asset lookup branch in
      `Web::App::_bundled_public_asset_path`, so clean Ubuntu runners close
      the final `99.9` statement gap and keep the reviewed `lib/` totals at
      `100.0 / 100.0 / 100.0`
4.12  2026-06-10
    - harden the GitHub-hosted `Devel::Cover` gate by exercising the
      low-traffic `Web::App` compatibility helpers directly in the focused web
      coverage tests, so route fan-out timing on CI runners cannot leave the
      reviewed `lib/` totals below `100.0 / 100.0 / 100.0`
4.11  2026-06-10
    - decouple the GitHub-hosted CPAN upload workflow from ordinary `vX.XX`
      tag pushes so signed GitHub release tags can be published without
      triggering an unasked PAUSE upload
    - broaden the CodeQL push trigger to every branch so repository-side SAST
      coverage is visible on more commit paths instead of only `master`
4.10  2026-06-10
    - move project-local isolated Docker Compose service discovery and
      disable markers under `./.developer-dashboard/config/docker/...` so
      the implementation matches the documented config-root contract across
      resolver lookup, toggle writes, and exported `DDDC` paths
4.09  2026-06-06
    - keep smart-routed skill page browser edits on their canonical
      `/app/<skill>` and nested `/app/<skill>/<sub-skill>` aliases by
      teaching the edit/source routes and Play handoff to load skill index
      pages through the same smart resolver instead of collapsing to the
      underlying `BOOKMARK: index` id
4.08  2026-06-06
    - split the Web UI bookmark editor into Jupyter-like section blocks while
      keeping the saved bookmark file format canonical, including block-aware
      syntax overlays, Tab-created sections, hidden source recomposition, and
      Play-mode save-and-render handoff
    - standardize operator-facing built-in CLI output so path, file, api, and
      skill inventory or mutation commands default to readable table summaries
      while `-o json` returns the full raw machine payload
4.04  2026-06-05
    - restore the public switchboard helper path contract for dotted
      `dashboard <skill>.<command>` dispatch, keep Windows core-backed
      built-ins routed through the shared staged `_dashboard-core` helper,
      and fix the staged private core import drift that broke `dashboard init`
      and later helper refreshes
    - harden Windows runtime helper and state-file flows so detached web,
      collector-loop, collector-worker, and watchdog launches use the staged
      foreground helper entrypoints and tolerate Windows rename collisions
      through explicit replace fallbacks
    - teach the Windows checkout bootstrap to seed `HOME` from PowerShell's
      own `HOME`, create a stable user-space `make.cmd` shim for Strawberry
      GNU make, and keep the fuzz gate lightweight under `Devel::Cover`
    - force non-interactive CPAN environment defaults for skill `cpanm`
      installs so Windows fresh-session bootstrap checks do not stall behind
      dependency configure prompts
4.03  2026-06-05
    - ship an explicit OWASP compliance SOW and evidence matrix, wire it into
      the OWASP and release-metadata tests, and tighten the manuals so the
      safe public wording stays `OWASP-aligned` / `OWASP-gated` until the
      remaining governance blockers are truly closed
4.02  2026-06-04
    - fix `_helper_file_supports_internal_command` substring detection so
      runtime helper refresh probes return true for matching staged helpers
      and stay stable under the plain and covered harnesses
    - harden `t/47-zombie-coverage-closure.t` with a persistent helper
      fixture and direct runtime-object assertions so the final gate loop
      stays deterministic
4.01  2026-06-02
    - add the built-in `dashboard api` management command for layered
      `config/api.json` machine auth, including merged registry listing,
      per-key inspection, SHA-256 hashing of raw secrets before persistence,
      optional `--maybe-secret` route-oriented secret input, exact saved
      `/ajax/...` route add/remove controls, duplicate-route no-op handling,
      and child-layer tombstones that hide inherited API groups without
      rewriting parent config
4.00  2026-06-02
    - add layered `config/api.json` machine auth for selected saved
      `/ajax/...` routes, including installed skill `config/api.json`
      fragments, exact route allowlists, and `X-DD-API-Key` plus
      `X-DD-API-Secret` header verification against stored SHA-256 digests
    - keep helper-session auth working on those same API-registered ajax
      routes while failing unauthenticated or wrong-secret machine callers
      closed with `403 {"status":"forbidden"}`
    - pass saved ajax API auth headers through the PSGI/Dancer adapter so
      browser-facing and direct HTTP entrypoints enforce the same machine-auth
      contract
3.99  2026-05-26
    - keep the active shell directory in dashboard-managed child `PATH`
      repairs alongside the current Perl interpreter bin so collector shell
      commands still execute cleanly during blank-environment tarball installs
      and any runtime that inherits a stripped `PATH`
3.98  2026-05-20
    - treat collector `disable` as a hard stop-and-skip flag so disabled
      collectors are not started, explicit named starts reject them, and any
      already-running managed loop is stopped during the next lifecycle action
    - remove managed collector indicators for disabled collectors instead of
      leaving stale active indicator state behind
    - normalize dashboard-owned `PERL5LIB` ordering so core/site/vendor Perl
      dirs stay ahead of inherited user-local shadow copies, preventing stale
      dual-life XS modules such as `Encode` from breaking helper startup,
      collector child commands, saved Ajax subprocesses, and skill hooks on
      hosts with older local-lib artefacts
    - keep the current Perl interpreter directory at the front of `PATH` for
      dashboard-managed child commands so collector loops, saved Ajax
      subprocesses, and skill hooks do not fall back to a mismatched system
      `perl` when they execute `dashboard` through `/usr/bin/env perl`
    - run collector shell commands through a non-login shell so macOS shell
      session restore output and other shell-init chatter do not get prefixed
      onto JSON collector output
    - slow tmux ticket/workspace status refresh from a 2-second hot loop to a
      15-second cadence so dashboard-managed tmux sessions do not burn CPU just
      to repaint status fragments
    - apply a 30-second default safety floor to shell collectors that recurse
      back into `dashboard` or `d2`, with explicit opt-outs and an environment
      override for intentionally fast dashboard polling
    - pause the collector watchdog supervisor during explicit named collector
      stop and restart actions, then restore supervision for the remaining
      watched fleet afterwards so watchdog restarts do not race manual
      lifecycle commands
    - preserve the saved managed web pid during stop flows even when shutdown
      has to fall back to persisted-state listener pids, and still escalate the
      saved pid to KILL when process discovery lags after TERM
    - scope web stop ajax cleanup to the current dashboard runtime root instead
      of sweeping every `dashboard ajax:` process owned by the user
    - make collector loop shutdown kill active worker process groups so
      long-running singleton commands stop with the collector loop instead of
      surviving as orphaned subprocesses
    - reap exited collector workers and watchdog-adopted dashboard children
      immediately on `SIGCHLD` instead of waiting for the next scheduler or
      watchdog tick, so long-interval collectors and orphaned supervisors do
      not leave visible `<defunct>` dashboard processes behind for minutes or
      days
    - persist active worker pids in collector loop state and lock in a live
      singleton regression where a long-running due run must be skipped instead
      of overlapped

3.92  2026-05-20
    - keep `dashboard ps1` on a lighter switchboard path by lazy-loading the
      unknown-command suggestion runtime and helper-staging-only modules
      instead of paying for them on every prompt render
    - stage only the requested built-in helper on the hot path and reuse one
      `PathRegistry` object across the switchboard invocation, instead of
      rescanning and restaging the entire helper tree for every prompt
    - load installed skill config fragments directly from layered
      `config/config.json` files in `Config` so prompt-time config reads do not
      pull in the full skill dispatch runtime

3.91  2026-05-20
    - make `dashboard ps1` skip `tmux show-environment` when `TMUX` is unset,
      stop constructing an unused collector object, and refresh only
      prompt-visible core indicators on the prompt path
    - read the prompt branch directly from `.git/HEAD` and worktree `gitdir:`
      metadata instead of spawning `git branch`, keeping branch rendering fast
      on slower hosts such as iSH
    - add a collector-sync fast path so repeated prompt renders avoid the full
      collector indicator rewrite path when the stored config-backed indicator
      state already matches

3.90  2026-05-20
    - make `PathRegistry` actually reuse a precomputed invocation cwd and
      memoize repeated DD-OOP-LAYERS path derivation so thin helper commands
      stop recalculating the same runtime chain during one process
    - make `EnvLoader` plain-directory traversal reuse the registry cwd cache
      instead of re-running `cwd()` while it walks root -> project -> leaf
      `.env` layers
    - tighten the nested skill env/compose fix set with the final blank-host
      and coverage gate closure work

3.89  2026-05-19
    - expand nested skill env loading from root to leaf for dotted skill
      commands and participating docker compose skill services, preserving
      overwritten parent values under cumulative aliases such as
      `foo_VERSION` and `foo_bar_VERSION`
    - discover nested installed-skill docker compose roots so participating
      leaf services such as `skills/foo/skills/bar/skills/zzz/config/docker`
      contribute both leaf `zzz_DDDC` and cumulative `foo_bar_zzz_DDDC`
      aliases during compose resolution
    - cover the remaining action-runner defensive and background-child startup
      branches so the final tree can close the 100% coverage gate again

3.88  2026-05-19
    - load participating skill-root `.env` files into `dashboard docker compose`
      resolution and export normalized `<skill>_DDDC` variables for each
      participating skill docker root
    - run detached background page command actions through a reaped supervisor
      path so blank-container tarball installs and root-owned hosts do not
      leave stale background wrappers behind or fail the tarball gate

3.86  2026-05-19
    - fixed packaged shell bootstrap generation so staged helpers always
      re-enter the active dashboard entrypoint instead of leaking a stale
      inherited source-tree path into extracted tarball installs and tests

3.85  2026-05-19
    - export one skill-specific <skill-name>_DDDC compose env variable for each participating skill docker root
    - keep participating skill .env loading isolated to docker compose resolution without executing skill .env.pl files

3.84  2026-05-19
    - load participating installed skill .env files into docker compose resolution without executing skill .env.pl files

3.83  2026-05-18
    - fixed interactive `dashboard skills install` progress so dependency rows
      are only added when the matching manifest file actually exists in the
      fetched skill, instead of printing `[OK] ... skipped: ... not present`
      noise for absent files
    - filtered operating-system-specific dependency rows so `aptfile`,
      `apkfile`, `dnfile`, `wingetfile`, and `brewfile` only appear on their
      matching host families and stay hidden on unrelated platforms even when
      those files exist in the skill
    - kept cross-platform manifest rows such as `package.json`,
      `requirements.txt`, `cpanfile`, `cpanfile.local`, `Makefile`, `ddfile`,
      and `ddfile.local` visible only when those files are present, which
      leaves the progress board focused on real work instead of skipped probes

3.79  2026-05-16
    - replaced the `tomq` backend dependency with `TOML::Parser`, inflating
      TOML booleans to plain Perl `1` and `0` scalars so query output stays
      stable while the blank-container `cpanm` install gate no longer trips
      over the failing upstream `TOML::Tiny 0.21` test suite on Perl 5.38

3.78  2026-05-16
    - fixed the tag-driven GitHub release workflow so it installs
      `Devel::Cover` before running the numeric coverage gate; this avoids
      the exit-127 `cover` failure that previously blocked signed GitHub
      release publication and kept Scorecard from seeing any releases

3.77  2026-05-15
    - aligned every shipped Perl module under `lib/` with the distribution
      version after the blank-host installer fix so the source tree, release
      metadata, and CI coverage gates all read one consistent `3.77` version
      instead of a mixed `3.75`/`3.76` module set

3.76  2026-05-15
    - fixed blank Ubuntu `curl ... | sh` bootstrap installs so `install.sh`
      seeds `File::ShareDir::Install` into `~/perl5` before `cpanm --notest .`
      runs against the cloned checkout, avoiding the configure-time
      `Can't locate File/ShareDir/Install.pm` failure on fresh hosts
    - aligned `install.ps1` with the same configure-prerequisite bootstrap so
      the checkout install path does not drift between Unix-like and Windows
      operators

3.75  2026-05-15
    - fixed Windows background web helper lookup so source-tree and CI test
      runs no longer die when `RuntimeManager` asks `File::ShareDir` for an
      installed dist share that does not exist yet; helper fallback now reuses
      the same guarded helper-asset resolver used elsewhere in the runtime
    - replaced the built-in `/js/jquery.js` shim with a bundled local copy of
      jQuery 4.0.0, while keeping `/js/jquery-4.0.0.min.js` as a compatibility
      alias for the same shipped asset
    - taught the browser bookmark editor to expand `:---` into the full
      separator line and insert the next sensible unique directive so common
      `TITLE -> HTML -> CODE<N>` authoring flows are smoother
    - added release and route coverage for the bundled jQuery asset and the
      missing-dist-share helper fallback path

3.74  2026-05-15
    - added direct `python` dispatch for executable `cli/*.py` commands and
      hook files so Python-backed skills now run through the same logical
      command and hook resolution path as Perl, Node, Go, Java, and shell
      helpers
    - added `requirements.txt` to the skill dependency chain after
      `package.json`, installing Python dependencies through
      `python -m pip install --user --requirement requirements.txt`
    - regenerated README.md from the canonical POD after the runtime and
      dependency-chain changes so release metadata stays in sync

3.73  2026-05-15
    - added direct `node` dispatch for executable `cli/*.js` commands and
      hook files, so JavaScript-backed skill commands now run through Node in
      the same extension-resolution path already used for Perl, Go, Java, and
      shell helpers
    - fixed the platform runnable-file resolver so logical command names can
      discover matching `.js` files on Unix and Windows without breaking the
      existing `package.json` dependency-install chain used by
      `dashboard skills install`
    - fixed the prompt tmux-status unit coverage to clear `WORKSPACE_REF`
      explicitly so ordinary tmux sessions still test the non-dashboard path

3.72  2026-05-14
    - fixed collector watchdog supervision so a managed loop that stays alive
      but stops making progress is treated as stalled, restarted
      automatically, and reported explicitly instead of sitting silent until
      an operator notices
    - renamed the primary tmux workflow from `dashboard ticket` to
      `dashboard workspace`, while keeping the older `dashboard ticket`
      spelling plus `TICKET_REF` session state as compatibility fallbacks
    - added layered plain-directory `.env` refresh for workspace sessions, so
      the highest ancestor `.env` acts as the base, parent `.env` files merge
      next, the current directory `.env` overrides last, and resumed tmux
      workspace sessions are refreshed in place with dropped keys unset

3.71  2026-05-14
    - fixed live collector status writes so managed indicator ordering now
      keeps the collector array order from `config/config.json` even after one
      collector refreshes its own state, instead of dropping back to
      alphabetical order in `/system/status`, page-header indicators, and
      `dashboard ps1`
    - fixed runtime `config/routes.json` aliases so normal saved bookmarks
      such as `/app/learn.ai` can be reached through shorter custom paths like
      `/java`, and widened that runtime alias loading across `/app`, `/ajax`,
      `/js`, `/css`, and `/others` instead of limiting custom paths to
      installed skill route metadata only

3.70  2026-05-13
    - moved skill route metadata to `config/routes.json` and changed the
      schema to the flat custom-path form, where each public path maps to one
      local smart route string or an object with `to` and optional `type`
    - expanded custom skill route support beyond Ajax so installed skills can
      publish fallback custom paths for `/app`, `/ajax`, `/js`, `/css`, and
      `/others` without breaking the smart longest-prefix parent routing
    - kept saved skill-page Ajax URL generation on the canonical custom path
      from `config/routes.json`, with Ajax custom routes defaulting to `json`
      unless an explicit route-level `type` is supplied
    - fixed browser `/system/status`, page-header status, and `dashboard ps1`
      indicator ordering so managed collector indicators now follow the
      collector array order from config instead of drifting alphabetically
    - fixed `dashboard serve logs -f` so a line appended immediately after
      the initial tail output is no longer skipped by a seek-to-end race
      before follow mode starts streaming new content

3.69  2026-05-13
    - added skill-local `dashboards/routes.json` metadata so installed skill
      Ajax handlers can publish canonical custom paths, optional fallback
      aliases, and default response types such as `json`, `html`, or raw
      mime strings without breaking the smart `/ajax/<repo>/...` resolver
    - fixed saved skill-page Ajax URL generation so `Ajax(file => ...)`
      emits the declared canonical custom route when route metadata exists,
      while the longest-prefix smart `/ajax/...` route remains the primary
      resolver and custom aliases are fallback-only before a normal `404`
    - fixed browser `/system/status`, page-header status, and `dashboard ps1`
      indicator ordering so managed collector indicators now follow the
      collector array order from config instead of drifting alphabetically

3.68  2026-05-13
    - added collector overlap control with explicit `mode` and `multiple`
      config fields, defaulting collectors to singleton scheduling and
      allowing bounded parallel runs only when a collector opts into
      `mode = multiple`
    - fixed collector loop scheduling so long-running jobs no longer block
      the next interval tick for opt-in multiple-mode collectors, while
      singleton collectors still suppress overlap by design
    - made collector status updates lock-safe so active-run counts and the
      `running` flag stay correct when overlapping collector workers finish
      concurrently

3.67  2026-05-13
    - fixed managed runtime child cleanup so detached web startup helpers,
      collector stop paths, the collector watchdog supervisor, SSL frontend
      connection workers, and background page actions no longer leave zombie
      processes behind on hosts such as macOS and WSL
    - fixed the SSL frontend accept loop so child-reaping `SIGCHLD` traffic
      no longer makes the public listener shut its backend down early, while
      TERM/INT/HUP now request a clean frontend shutdown explicitly instead
      of depending on interrupted `accept()` side effects
    - tightened regression coverage so collector stop and watchdog shutdown
      tests fail if the runtime leaves direct child processes unreaped, and
      action/background SSL runtime tests now enforce the no-zombie contract

3.66  2026-05-08
    - added a collector watchdog supervisor that restarts unexpectedly-dead
      managed collector loops after startup instead of leaving them silently
      stopped until an operator notices
    - persisted watchdog restart counters, timestamps, error text, and
      `attention_required` collector status metadata so repeated crash loops
      are explicit in `dashboard collector status <name>` and collector logs
    - stopped the watchdog from thrashing forever by raising an explicit
      attention-required state after too many crashes inside the watchdog
      window, forcing a visible operator follow-up instead of silent churn

3.64  2026-05-08
    - fixed macOS Terminal.app shell bootstrap to source /etc/bashrc_Apple_Terminal
      when update_terminal_cwd is missing, preventing "command not found"
      errors on fresh macOS bash sessions

3.63  2026-05-08
    - added dockerfile manifest support to skill installation, allowing skills
      to declare Docker images that get built automatically during install
    - fixed Progress.pm to support unlimited streaming output for long-running
      operations like Docker builds, with optional max_detail_lines cap
    - added has_dockerfile metadata to skill list and usage output
    - fixed macOS Terminal.app shell bootstrap to source /etc/bashrc_Apple_Terminal
      when update_terminal_cwd is missing, preventing "command not found"
      errors on fresh macOS bash sessions

3.62  2026-05-07
    - remove matching root ddfile skill source entries when uninstalling a skill

3.61  2026-05-06
    - switched the project license from Perl_5 to MIT across the repository
      metadata, shipped LICENSE text, README, canonical POD, and Scorecard
      guardrails so the published license state is explicit and consistent
    - removed the old Artistic sidecar license file and Perl dual-license
      wording so the repo no longer advertises conflicting licensing terms

3.60  2026-05-06
    - documented the open-source license disclaimer more explicitly in the
      canonical POD and synced README, making it clear that the software is
      provided "as is", no warranty is given, and normal free-software use is
      covered by the project's baseline liability disclaimer

3.59  2026-05-06
    - fixed shared skill-nav discovery so nested installed skill trees such
      as `skills/ho/skills/coverage/dashboards/nav/index.tt` now render on
      the nested skill route itself and also join the shared nav strip above
      normal saved pages such as `/app/index`
    - taught the skill dispatcher to recurse through nested installed
      `skills/<repo>` trees when collecting skill nav pages for the web UI,
      while preserving deterministic order and disabled-skill filtering
    - fixed layered skill nav route-id discovery so `dashboards/nav/*`
      recursively includes nested fragment paths instead of only direct files
      in the first `nav/` directory

3.58  2026-05-06
    - stamped dashboard-managed helper bodies with an explicit
      `developer-dashboard-managed-helper-version` marker so installed hosts
      can prove which helper build they actually have when staged helper
      behavior drifts from the active tarball
    - fixed Debian-family bash bootstrap installs and repairs so
      dashboard-managed shell lines are kept above the standard
      non-interactive `return` guards in `~/.bashrc`, including the Ubuntu
      single-line `[ -z "$PS1" ] && return` form, which keeps
      `dashboard ticket` tmux status commands working on installed hosts such
      as `hp.local`
    - taught `dashboard doctor` to audit and repair that misplaced
      `~/.bashrc` bootstrap shape with `dashboard doctor --fix`
    - fixed Windows `.cmd` and `.bat` command dispatch on Linux, WSL, and
      packaged-install hosts so an extensionless local `cmd` helper found in
      `PATH` is normalized back to `cmd.exe` instead of being treated as a
      custom command processor
    - taught `dashboard doctor` to audit staged helper drift under
      `~/.developer-dashboard/cli/dd/`, report stale or missing
      dashboard-managed helpers such as `_dashboard-core`, and restage them
      with `dashboard doctor --fix` when the currently shipped helper assets
      can repair the runtime
    - added direct regression coverage for extensionless `cmd` shims, stale
      helper staging, and shell bootstrap drift through both source-tree and
      packaged-install paths
    - fixed packaged helper asset discovery so tarball, PAUSE, and `cpanm`
      install-test trees keep resolving `share/private-cli` from a stable
      absolute module source path even after later `chdir` calls in
      long-running test processes
    - hardened the post-build smart-router two-stage Docker guard so one
      transient upstream `cpanm` fetch or unpack failure is retried once
      before the repository packaging gate is treated as broken
    - fixed the installer docs so they match the real checkout/master
      bootstrap behavior and the current shell-bootstrap repair workflow

3.45  2026-05-05
    - fixed Unix-like `install.sh` so blank streamed installs no longer fall
      back to the stale `Developer::Dashboard` CPAN target; when no explicit
      `DD_INSTALL_CPAN_TARGET` override is set they now install the current
      checkout directly or clone GitHub `master` into a temporary checkout
      for `curl ... | sh` style bootstraps
    - added `tmux` to the shipped `aptfile`, `apkfile`, `dnfile`, and
      `brewfile` bootstrap package sets because `dashboard ticket` is a
      first-party tmux workflow
    - fixed blank Ubuntu bootstrap coverage so Debian-family installer docs
      and tests now prove the extra XS build header requirement for the live
      `cpanm` dependency chain instead of silently assuming the base image
      already ships it

3.43  2026-05-05
    - fixed managed helper staging so upgraded home runtimes remove
      dashboard-owned older flat helpers from `~/.developer-dashboard/cli/`
      and keep the active built-in helper surface converged on
      `~/.developer-dashboard/cli/dd/`
    - added direct staged-helper regression coverage so the managed
      `dashboard shell bash` helper itself must emit the ticket tmux status
      bootstrap after staging, not just the repo checkout command path
    - fixed the blank-environment integration image so host-built tarball
      installs now include the native CPAN build packages required by
      XML::Parser, Net::SSLeay, and related transitive prerequisites
    - switched the blank-environment integration image to a real Debian
      Chromium runtime so browser smoke checks no longer die on the Ubuntu
      snap-wrapper launcher stub
    - fixed the host-side blank-environment launcher so it rebuilds the
      integration image from the current Dockerfile before running the
      installed-runtime smoke, preventing stale cached images from hiding
      browser-runtime fixes

3.42  2026-05-05
    - fixed blank-mac `curl ... | sh` bootstrap so `install.sh` now
      bootstraps Homebrew automatically before reading the repo `brewfile`
      and continuing the normal macOS package flow
    - fixed Linux runtime lifecycle isolation so `dashboard restart` and
      `dashboard stop` no longer adopt or kill Developer Dashboard web and
      collector pids that belong to a different pid namespace such as a
      sibling Docker container
    - added Docker-style rolling detail lines to `dashboard skills install`
      so long-running dependency manifests keep the epic task board visible
      while streaming the newest manifest output under the active step
    - fixed tmux ticket-session prompt/status behavior so existing and new
      `dashboard ticket` tmux sessions suppress inline prompt indicators,
      move the full indicator strip into the first row of a two-line bottom
      tmux status block, keep tmux's normal indexed session/window row
      beneath it, preserve TT-backed percentage indicators and live
      collector values, refresh automatically through tmux status updates,
      and leave ordinary tmux sessions on the normal inline prompt path

3.40  2026-05-05
    - moved prompt indicators out of inline shell prompts and into tmux
      `status-right` whenever the generated shell bootstrap runs inside tmux,
      while keeping the existing prompt indicators outside tmux
    - added a dedicated `dashboard ps1 --mode tmux-status` formatter and a
      `--no-indicators` prompt path so bash, zsh, POSIX sh, and PowerShell
      all share the same tmux-aware indicator behavior

3.37  2026-05-02
    - added an explicit InternalCLI coverage guard for installed layouts where
      File::ShareDir already returns the private-cli root, keeping the numeric
      Devel::Cover gate at 100 percent after the Windows bootstrap fixes
    - added a Windows smoke guard that re-enters a fresh profile-loaded
      PowerShell session after install.ps1 so the streamed bootstrap must
      resolve `dashboard`, print `dashboard version`, and run
      `dashboard logs`

3.36  2026-05-02
    - fixed the generated PowerShell profile bootstrap so fresh Windows
      sessions no longer try to pass the multi-line `dashboard shell ps`
      output array straight into `Invoke-Expression`
    - fixed the streamed Windows installer so `irm .../install.ps1 | iex`
      now writes a self-contained profile block that can load `dashboard`,
      `local::lib`, and the PowerShell prompt bootstrap in future sessions

3.35  2026-05-02
    - fixed the installed private helper asset lookup on Windows so
      `dashboard init` no longer stops at an empty
      `MSWin32-x64-multi-thread/auto/Developer/Dashboard/private-cli`
      directory when the real shipped helper assets live under
      `auto/share/dist/Developer-Dashboard/private-cli`
    - fixed the streamed Windows checkout bootstrap so a blank host can now
      finish `dashboard init` after `irm .../install.ps1 | iex`, stage the
      home helper runtime, and continue into the generated PowerShell shell
      bootstrap cleanly

3.34  2026-05-02
    - fixed the installed private helper asset lookup so Windows local::lib
      installs no longer look for `_dashboard-core` under the wrong arch-auto
      path when the shared dist asset root actually lives under
      `auto/share/dist/Developer-Dashboard/private-cli`
    - fixed the streamed PowerShell bootstrap so `install.ps1` now runs
      `dashboard init` before activating `dashboard shell ps` in the current
      shell, ensuring the staged home helper runtime exists before the
      bootstrap asks for PowerShell shell wiring
    - fixed the generated PowerShell profile guard so future sessions only ask
      `dashboard shell ps` for bootstrap output after the staged home helper
      runtime exists under `~/.developer-dashboard/cli/dd/_dashboard-core`

3.33  2026-05-01
    - fixed the packaged install metadata so blank Windows hosts no longer
      pull test-only dependencies such as `Plack::Test` and `Test::Pod`
      during `cpanm --notest Developer::Dashboard`, which previously dragged
      the `Test::SharedFork` chain into the streamed PowerShell bootstrap and
      broke fresh installs
    - fixed the streamed Windows checkout bootstrap so it now installs from
      the current GitHub checkout shape cleanly on Windows, including the
      Windows-safe `Makefile.PL` version path and the self-healing private
      helper staging path used by lightweight commands such as `dashboard
      encode`
    - fixed the PowerShell bootstrap so `install.ps1` now sets the CurrentUser
      execution policy to `RemoteSigned` before writing the generated profile,
      allowing new PowerShell sessions to load the `dashboard shell ps`
      bootstrap instead of failing with `running scripts is disabled`
    - fixed the generated PowerShell profile block so `PERL_MB_OPT` is now
      written with format-string quoting instead of nested literal quotes that
      broke new sessions with `Unexpected token '$ddInstallRoot""'`
    - replaced the repository PSGI test dependency on `Plack::Test` with a
      local harness under `t/lib` so the release metadata can stay focused on
      real runtime requirements while the web route coverage still stays
      intact
    - corrected the install bootstrap docs so the Windows examples use the
      `install.ps1` entrypoint and describe the lighter packaged install path

3.30  2026-05-01
    - fixed the streamed Windows bootstrap so native `winget` command output
      is written to the host terminal instead of leaking into typed helper
      return values, which previously broke the Strawberry Perl path hand-off
      during `irm .../install.ps1 | iex`
    - fixed the Windows Perl bootstrap so `install.ps1` no longer tries to
      self-install `App::cpanminus` while the downloaded `cpanm` bootstrap
      script is still running, avoiding the Windows file-replacement failure
      that broke the `local::lib` setup step on blank hosts
    - documented the exact streamed PowerShell bootstrap command as
      `irm https://raw.githubusercontent.com/manif3station/developer-dashboard/refs/heads/master/install.ps1 | iex`
      and updated the Windows install docs to describe the standalone
      `cpanmin.us` plus `local::lib` bootstrap flow accurately

3.28  2026-05-01
    - renamed the Windows checkout bootstrap entrypoint from `install.ps` to
      `install.ps1` so the shipped filename matches normal PowerShell script
      expectations while keeping the streamed `irm ... | iex` flow explicit
    - hardened the Windows bootstrap winget path so package installs are
      pinned to the community `winget` source and the installer performs one
      `winget source reset --force` plus source refresh retry before failing
      with a clear HRESULT-style exit code

3.27  2026-05-01
    - added a repo-root `install.ps1` bootstrap entrypoint for Windows
      PowerShell hosts so blank-machine installs can bootstrap Git,
      Strawberry Perl, Node.js, user-space Perl tooling, and the dashboard
      runtime from a checkout or a streamed `irm ... | iex` flow
    - updated the install bootstrap docs, release metadata guards, and
      shipped tarball asset checks so the Windows bootstrap path is tracked
      alongside the existing Unix-like `install.sh` flow

3.26  2026-04-30
    - changed the blank-container tarball installation gate to use
      `cpanm --notest` after the normal source-tree test and coverage gates
      have already passed, so packaged dependency resolution and installed
      runtime behavior stay verified without rerunning the full tarball test
      suite
    - updated the blank-environment integration runner, release docs, and
      integration guardrails so the post-build `t/44-smart-router-two-stage.t`
      check and the broader tarball integration flow both follow the same
      `cpanm --notest` policy

3.25  2026-04-29
    - fixed the installed Dancer web stack so the public `/app`, `/ajax`,
      `/js`, `/css`, and `/others` routes now reuse the backend smart route
      dispatcher instead of bypassing skill-local namespace resolution
    - added PSGI coverage that proves top-level and nested skill-local app,
      ajax, js, css, and others routes all work through the real installed
      web route layer
    - added the post-build `t/44-smart-router-two-stage.t` guard and wired the
      blank-environment host integration launcher to run it immediately after
      `dzil build` so extracted-dashboard routing regressions are caught at the
      tarball stage instead of only in source-tree tests
    - fixed helper-login blank-editor rendering so a helper user who lands on
      the root editor path still sees helper chrome, including the logout link
      and helper username, during installed integration runs

3.24  2026-04-29
    - added skill-local web namespaces for `dashboards/ajax/*` and
      `dashboards/public/{js,css,others}/*` so installed skills can serve
      saved Ajax handlers and public assets without copying them into the
      shared dashboard roots
    - added longest-prefix route resolution so `/app`, `/ajax`, `/js`,
      `/css`, and `/others` can distinguish skill prefixes from normal nested
      global paths and can also resolve nested child skills under
      `skills/<repo>/...`
    - kept nested global saved-bookmark assets and saved Ajax files working
      even when their leading path segments match an installed skill name

3.23  2026-04-29
    - fixed the README source-of-truth process so the checked-in README.md is
      regenerated from the canonical Developer::Dashboard POD instead of being
      hand-edited independently
    - added a checkout sync helper and release-metadata guard so README.md
      must exactly match the generated Markdown output from the canonical POD
    - fixed the JS fast-check wrapper so npm update-notifier chatter does not
      break the stderr-clean fuzz gate on newer npm releases

3.22  2026-04-29
    - regenerated README.md as real Markdown instead of accidental POD content
    - added a release-metadata guard so README.md cannot silently regress back
      into POD format

3.21  2026-04-29
    - removed the final stale API Dashboard POD example from the public
      dashboard entrypoint so the extracted API and SQL dashboards are now
      fully pruned from core code, docs, POD, tests, and shipped assets

3.20  2026-04-29
    - fixed bare dashboard skills install so a first successful install from
      the home root ddfile is reported as installed even when the skill ships
      no .env VERSION metadata, instead of showing unknown with blank version
      markers
    - fixed named collector stop summaries so dashboard stop collector NAME
      still reports the stopped collector row when the managed loop is alive
      but its process title has not become observable yet

3.19  2026-04-29
    - extracted optional browser workspaces out of the core distribution,
      leaving the core runtime seeded-page set and manuals focused on the
      features that still ship in Developer Dashboard itself
    - removed core seeded-page handling, runtime bootstrap wiring, release
      metadata assertions, and shipped browser tests for the extracted
      optional browser workspaces
    - moved the extracted page assets, dedicated tests, and supporting
      notes out of the core repository tree so the default core tarball no
      longer carries that optional feature set

3.19  2026-04-29
    - fixed dashboard stop and restart so they follow the saved managed
      listener port back to the real serving pid when the live web process
      renames itself into the underlying starman master listener shape
    - fixed minimal Docker lifecycle control so container stop/restart runs no
      longer lose ownership of the active listener after startup
