From 38a5382a856c7fdd6defbdbfdc4ed8735b740f1e Mon Sep 17 00:00:00 2001 From: Albert Hui Date: Thu, 20 Aug 2026 21:36:57 +0000 Subject: [PATCH 1/2] ci: migrate the commodity jobs to the shared fleet workflow Five of eleven ci.yml jobs move to the shared workflow (test, coverage, secrets, deny, vet) and it publishes `ci / All checks`. The six that CANNOT move go to `extras.yml`, with the reason each cannot stated in the file. `deb-verify`, `macos-verify`, `windows-verify`, `fuzz`, `docs`, `release-plz` and `release` are untouched. COVERAGE KEEPS ITS FULL STRENGTH: strict on lines AND functions, matching the bespoke gate rather than degrading to a floor. That was not possible until today. Pointing the shared gate at this repo's real coverage produced 156 line and 693 function failures where the bespoke gate passed, and every one was the shared gate being wrong -- it demanded a marker on every line of a multi-line dead arm, and counted a crate compiled twice as an uncovered function. Both are fixed upstream, and this repo is what found them. One genuine gap surfaced in the process: the decode-failure arm in `holiday::load_or_empty` is provably dead (the embedded blobs are validated in CI) and had no marker. It has one now, with the invariant stated. WHAT extras.yml KEEPS, and why none of it fits the shared workflow: MSRV 1.75 (timeglyph-core) the shared msrv job builds IN-TREE, and this repo's Cargo.lock is v4, which cargo 1.75 cannot parse. Verifying the floor means copying the crate out -- and asserting the resolved lock names exactly one package, which is what makes "zero dependencies" a checked claim. Lens build `lens/` is EXCLUDED from the workspace, so no `--workspace` invocation reaches it. lean-build asserts clap/csv/tracing-subscriber are ABSENT from the `--no-default-features` tree. The shared workflow builds; it does not assert absence. wasm-build, python-wheel wasm32 and maturin, neither expressible. freshness `cargo update --locked` on both lockfiles. These are not gated by `ci / All checks` -- a job outside the reusable workflow publishes its own context. `MSRV 1.75 (timeglyph-core)` is therefore required by branch protection ALONGSIDE the aggregate gate: it verifies a published floor downstreams pin against, and a promise nothing enforces is not a promise. Also fixed, both surfaced by gates this repo had never run: rustdoc 15 broken intra-doc links, none of which had failed a build because nothing ran rustdoc with `-D warnings`. Each fixed by what the item actually IS: public items in sibling modules got paths; `ColorMode::Mono` is a variant and needed its enum; `MAX_CARVE_BYTES`, `PackedCodec`, `datetime_candidates` and `hex_candidates` are PRIVATE and cannot be linked from public docs at all, so they are code spans. packaging `timeglyph-core` is PUBLISHED and shipped without a README or a LICENSE -- the crates.io page was blank and the licence named in the manifest had no text beside it. Both now ship. That is the third repo today with this defect, all found by the same check. Pre-flighted locally on a clean target, reading each command's own exit code: fmt, clippy -D warnings, tests, rustdoc, deny, vet, unsafe-audit, and the strict coverage gate on lines, functions and both. --- .github/workflows/ci.yml | 358 ++--------------------------------- .github/workflows/extras.yml | 231 ++++++++++++++++++++++ core/Cargo.toml | 1 + core/LICENSE | 202 ++++++++++++++++++++ core/README.md | 29 +++ core/src/lib.rs | 2 +- src/cal.rs | 2 +- src/cal_color.rs | 4 +- src/cal_render.rs | 2 +- src/carve.rs | 4 +- src/holiday.rs | 6 +- src/interpret.rs | 2 +- src/lunisolar.rs | 2 +- src/mcp.rs | 2 +- src/registry.rs | 2 +- src/scan.rs | 8 +- 16 files changed, 501 insertions(+), 356 deletions(-) create mode 100644 .github/workflows/extras.yml create mode 100644 core/LICENSE create mode 100644 core/README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 979269f..b384915 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,346 +4,24 @@ on: push: branches: [main] pull_request: - branches: [main] -env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: "0" +permissions: + contents: read jobs: - test: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - # Pin to the same toolchain as rust-toolchain.toml (timeglyph is an app: - # declared MSRV == the pinned toolchain). Do not float `toolchain: stable`. - - uses: dtolnay/rust-toolchain@1.96.0 - with: - components: clippy, rustfmt - - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - cache-on-failure: true - # --workspace / --all are load-bearing: with a root package present, cargo's - # default selection is that package ALONE, so a bare `cargo test` would - # silently skip `core/` (timeglyph-core) and its tests would never gate. - - run: cargo test --workspace --all-features - - run: cargo clippy --workspace --all-targets --all-features -- -D warnings - - run: cargo fmt --all --check - - # The lens (GUI overlay) is a separate crate, excluded from the root workspace, - # so the `test` job never touches it. Build it natively on each shipped - # platform to compile + LINK the platform picker CI otherwise never sees — UI - # Automation on Windows (x86_64, the exact shipped target), the Accessibility - # API on macOS, and AT-SPI on Linux. - lens: - name: Lens build (${{ matrix.os }}) - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Install GUI build deps (Linux) - if: runner.os == 'Linux' - run: | - sudo apt-get update - # mesa-vulkan-drivers supplies the lavapipe software Vulkan ICD. The lens's - # offscreen render gate asks wgpu for an adapter, and a runner with no ICD has - # none — the gate would panic rather than check anything. The all-black / - # tofu frame regression it catches (see Cargo.toml on default_fonts) is - # platform-independent and worth running here, so give it an adapter. - sudo apt-get install -y libxkbcommon-dev libgl1-mesa-dev libwayland-dev \ - libx11-dev libxcursor-dev libxrandr-dev libxi-dev libxcb1-dev pkg-config \ - mesa-vulkan-drivers - - uses: dtolnay/rust-toolchain@1.96.0 - with: - components: clippy, rustfmt - - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - cache-on-failure: true - - name: Build lens (compiles + links the platform picker) - working-directory: lens - run: cargo build --all-targets - # Gate the lens's own tests, not just that it compiles. Without this the - # clipboard privacy/caption tests and the offscreen render gate are local-only, - # so a regression in them reaches main green. - - name: Test lens - working-directory: lens - run: cargo test - - name: Clippy lens - working-directory: lens - run: cargo clippy --all-targets -- -D warnings - - name: Rustfmt lens - working-directory: lens - run: cargo fmt --check - - coverage: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: dtolnay/rust-toolchain@1.96.0 - with: - components: llvm-tools-preview - - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - cache-on-failure: true - - uses: taiki-e/install-action@a402910a723481c4c80d006d75298c796a9c8695 # cargo-llvm-cov - with: - tool: cargo-llvm-cov - # All test targets, all features (integration tests carry the coverage, so - # NOT --lib). The gate requires 100% LINE **and** function coverage of the - # LIBRARY (ADR-0008), honoring `// cov:unreachable` markers on - # provably-dead defensive arms (raw --fail-under-lines can't exempt - # those); main.rs (the Humble CLI shell) is excluded by the script. - # The JSON carries per-function counts, the LCOV per-line `DA:` hits — the - # gate needs both, and exits 2 if either is missing. - # - # BOTH commands must name every workspace member. `report` does not inherit - # the package selection of the run before it and rejects `--workspace`, so - # with a bare `report --lcov` the LCOV omits `core/` entirely — the LINE - # half of the gate then passes over timeglyph-core having measured nothing, - # while the JSON/function half still sees it. That asymmetry is what makes - # the hole quiet, so a new member must be added to the `-p` list here. - - name: Coverage + 100% line/function gate - run: | - cargo llvm-cov --workspace --all-features --json --output-path cov.json - cargo llvm-cov report -p timeglyph -p timeglyph-core --lcov --output-path lcov.info - python3 scripts/coverage-gate.py cov.json lcov.info - - name: Upload to Codecov - uses: codecov/codecov-action@0f8570b1a125f4937846a11fcfa3bcd548bd8c97 # v4.6.0 - with: - files: lcov.info - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: false - - msrv: - # timeglyph-core declares `rust-version = "1.75"` — a low floor downstreams - # (FOUNDATION crates, published low-MSRV parsers) pin against. An MSRV nobody - # enforces is an unbacked promise, so this job is what makes the number true. - # Raising the floor is near-breaking; do it deliberately, in both places the - # assert below names. - # - # It builds core/ COPIED OUT of the repo, and that is load-bearing rather - # than incidental. Two things in the tree would otherwise decide the outcome - # instead of the crate: the workspace Cargo.lock is version 4, which cargo - # 1.75 refuses to parse at all (it fails before ever reading core/), and - # rust-toolchain.toml pins 1.96.0, which would override the toolchain this - # job just installed. Outside the repo the crate is its own workspace with - # its own lockfile — exactly how a crates.io consumer builds it. Do NOT - # "simplify" this by downgrading or deleting the committed v4 lockfile. - name: MSRV 1.75 (timeglyph-core) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: dtolnay/rust-toolchain@1.75.0 - - name: Assert the declared MSRV is the one this job checks - run: | - declared=$(grep -m1 '^rust-version' core/Cargo.toml | cut -d'"' -f2) - if [ "$declared" != "1.75" ]; then - echo "core/Cargo.toml declares rust-version = \"$declared\", but this job installs 1.75.0." - echo "The two must move together, or the floor stops being verified. Update both." - exit 1 - fi - - name: Check + test timeglyph-core on its declared MSRV, standalone - run: | - dest="$RUNNER_TEMP/timeglyph-core-msrv" - cp -R core "$dest" - cd "$dest" - # `cargo +1.75.0`, not a bare `cargo`: the explicit +toolchain sits at - # the top of rustup's precedence chain, above RUSTUP_TOOLCHAIN, a - # directory override and any rust-toolchain.toml. A bare `cargo` here - # is silently hostage to ambient state — verified locally, where an - # exported RUSTUP_TOOLCHAIN=1.96.0 beat `rustup override set 1.75.0` - # and compiled the crate on 1.96 while the job looked green. - # - # Then assert the compiler rather than merely logging it. If the - # toolchain ever fails to resolve as intended, a newer cargo would - # build this crate happily and the job would pass having verified - # nothing — the vacuous-gate shape, same as a coverage report that - # contains no records for the file it claims to cover. - version=$(cargo +1.75.0 --version) - echo "$version" - case "$version" in - "cargo 1.75.0"*) ;; - *) echo "Expected cargo 1.75.0, got: $version — the MSRV was not actually exercised."; exit 1 ;; - esac - cargo +1.75.0 check --all-targets - cargo +1.75.0 test - - name: Assert zero dependencies - # The zero-dependency promise is what lets a downstream link this crate - # for free; enforce it rather than trust the manifest comment. The - # lockfile cargo just resolved must name exactly one package — itself. - run: | - dest="$RUNNER_TEMP/timeglyph-core-msrv" - count=$(grep -c '^\[\[package\]\]' "$dest/Cargo.lock") - if [ "$count" != "1" ]; then - echo "timeglyph-core resolved $count packages; it must resolve exactly 1 (itself):" - grep '^name = ' "$dest/Cargo.lock" - exit 1 - fi - - secrets: - name: Secret Scan (gitleaks) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - fetch-depth: 0 - # The gitleaks GitHub Action requires a paid license for org repos; the - # gitleaks CLI is free. Install + run it directly (fleet pattern). - - name: Install gitleaks - # Pin the version rather than query the GitHub API for "latest": the - # unauthenticated api.github.com call rate-limits on shared CI IPs, which - # left VERSION empty and 404'd the download. Bump deliberately. - shell: bash - run: | - set -euo pipefail - # renovate: datasource=github-releases depName=gitleaks/gitleaks - VERSION=8.30.1 - # Retried: a one-off network blip on a shared runner should not fail a - # job that has nothing to do with the network. Bounded so it cannot - # become a hang either. - fetch() { - local url="$1" out="$2" attempt=1 - until curl -sSfL --max-time 120 "$url" -o "$out"; do - if [ "$attempt" -ge 3 ]; then - echo "::error::download failed on all 3 attempts: $url (endpoint unreachable, not a code failure)" - return 1 - fi - echo "::warning::download failed (attempt ${attempt}/3), retrying: $url" - rm -f "$out" - sleep $((attempt * 15)) - attempt=$((attempt + 1)) - done - } - fetch "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_x64.tar.gz" /tmp/gitleaks.tar.gz - tar xz -C /tmp -f /tmp/gitleaks.tar.gz gitleaks - /tmp/gitleaks version - - name: Run gitleaks - run: /tmp/gitleaks detect --source . --config .gitleaks.toml - - deny: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - - name: Install cargo-deny - uses: taiki-e/install-action@a402910a723481c4c80d006d75298c796a9c8695 - with: - tool: cargo-deny - - name: Check advisories, licenses, bans, sources - run: cargo deny check - - freshness: - # Advisory dependency-freshness gate: fails if the committed Cargo.lock is - # stale relative to the requirements (see CLAUDE.md "Dependency Freshness"). - runs-on: ubuntu-latest - continue-on-error: true - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable - # BOTH locks. `lens/` is excluded from the workspace and carries its own - # Cargo.lock, which this job never looked at -- so it drifted while the - # check reported on the root alone and read as covering the repo. A gate - # that inspects one of two artifacts is not a gate on the repo. - - run: cargo update --locked - - run: cargo update --locked --manifest-path lens/Cargo.toml - - lean-build: - # Gate: `cargo add timeglyph --no-default-features` must NOT pull in clap, - # csv, or tracing-subscriber. Those are CLI-only deps; a library consumer - # doing `default-features = false` should get a lean build with only the - # core engine deps (jiff, thiserror, hex, serde, serde_json, tracing facade). - # Enforced now that clap/csv/tracing-subscriber sit behind the `cli`/`csv` - # features (a lean library build carries none of them). - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: dtolnay/rust-toolchain@1.96.0 - - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - cache-on-failure: true - - name: Lean lib build (no default features) - run: cargo build --no-default-features - - name: Assert clap / csv / tracing-subscriber absent from lean tree - run: | - cargo tree --no-default-features -e no-dev --prefix none > /tmp/lean-tree.txt - UNWANTED=$(grep -E '^clap |^csv |^tracing-subscriber ' /tmp/lean-tree.txt || true) - if [ -n "$UNWANTED" ]; then - echo "FAIL: CLI-only deps present in lean (no-default-features) build:" - echo "$UNWANTED" - exit 1 - fi - - wasm-build: - # Gate: the core engine must compile to wasm32 so the WASM playground and - # browser / CyberChef embeddings stay buildable. Depends on the engine being - # pure (no I/O, no clap/csv) and jiff + serde being wasm-compatible. - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: dtolnay/rust-toolchain@1.96.0 - with: - targets: wasm32-unknown-unknown - - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - cache-on-failure: true - - name: WASM lib build (wasm32-unknown-unknown, no default features) - run: cargo build --lib --no-default-features --target wasm32-unknown-unknown - - name: WASM playground binding build (wasm32) - run: cargo build --manifest-path wasm/Cargo.toml --target wasm32-unknown-unknown - - python-wheel: - # Gate: the PyO3 Python bindings build a wheel and its tests pass. Publishing - # to PyPI is a separate credentialed release step; this proves the wheel is - # always buildable and correct (plaso/Timesketch/Jupyter consume it). - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - uses: dtolnay/rust-toolchain@1.96.0 - - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 - with: - cache-on-failure: true - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: "3.11" - - run: pip install maturin pytest - # One v* tag ships the crate AND the wheel, so the wheel version must equal - # the crate version. release-plz bumps ONLY the root crate on a release, so an - # equality *check* here can never pass on a release PR (bindings/ would still - # lag). Instead stamp the bindings from the crate — the single source of truth — - # making drift impossible by construction (this closes the 0.3.0-vs-0.4.0 gap - # without a guard release-plz can't satisfy). The same stamp runs in the wheel - # build (release.yml) so published wheels are always at the crate version. - - name: sync bindings version to the crate - run: ./bindings/python/sync-version.sh - - name: Build + test the Python wheel - working-directory: bindings/python - run: | - maturin build --release - pip install target/wheels/*.whl - pytest tests/ -q - - vet: - name: Cargo Vet (supply-chain) - runs-on: ubuntu-latest - # Complements `deny` (known-bad advisories/licenses) with the supply-chain- - # injection layer: every dependency version must be human-source-reviewed or - # covered by an imported aggregate audit set (Google/Mozilla/Bytecode-Alliance/ - # Embark). Config in supply-chain/{config,audits,imports}.toml. - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: dtolnay/rust-toolchain@stable - - name: Install cargo-vet - uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6 - with: - tool: cargo-vet - - name: Fetch dependencies - run: cargo fetch - - name: Check supply chain - run: cargo vet --locked + ci: + uses: SecurityRonin/fleet-ci/.github/workflows/rust-ci.yml@1721d98b44b10fec8902969f80fa8b4ee5c06960 + with: + # STRICT on lines AND functions -- what the bespoke gate this replaces + # enforced, and stronger than the line-only gate 47 consumers run. It is + # only expressible now: the shared gate previously demanded a marker on + # every line of a multi-line dead arm, and counted duplicate crate + # compilations as uncovered functions. Both are fixed upstream. + coverage-gate: strict + coverage-metric: both + coverage-ignore-regex: '(^|/)src/(main\.rs|bin/)' + # The workspace floor. `timeglyph-core`'s published 1.75 promise CANNOT be + # verified in-tree -- this repo's Cargo.lock is v4 and cargo 1.75 refuses + # to parse it -- so that check stays in extras.yml, where it copies the + # crate out first, and is required by branch protection alongside this gate. + msrv: "1.96.0" diff --git a/.github/workflows/extras.yml b/.github/workflows/extras.yml new file mode 100644 index 0000000..747c2e1 --- /dev/null +++ b/.github/workflows/extras.yml @@ -0,0 +1,231 @@ +# The checks that the shared fleet workflow cannot express, carved out of +# ci.yml when the commodity jobs moved to it. +# +# These are NOT part of `ci / All checks`. A job outside the reusable workflow +# publishes its own context, so branch protection reaches it only if it is named +# there explicitly. `MSRV 1.75 (timeglyph-core)` is required alongside the +# aggregate gate for exactly that reason -- it verifies a published floor that +# downstreams pin against, and a promise nothing enforces is not a promise. +# +# Why each cannot move: +# +# MSRV 1.75 (timeglyph-core) the shared msrv job builds in-tree, and this +# repo's Cargo.lock is v4, which cargo 1.75 cannot +# parse. Verifying the floor means copying the +# crate out first -- and asserting the resolved +# lock names exactly one package, which is what +# makes "zero dependencies" a checked claim rather +# than a README sentence. +# Lens build `lens/` is EXCLUDED from the workspace, so no +# `--workspace` invocation reaches it. +# lean-build asserts clap/csv/tracing-subscriber are ABSENT +# from the `--no-default-features` tree. The shared +# workflow builds; it does not assert absence. +# wasm-build a wasm32 target build plus an out-of-workspace +# binding crate. +# python-wheel maturin + pytest against the built wheel. +# freshness `cargo update --locked` on both lockfiles. +name: extras + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + lens: + name: Lens build (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Install GUI build deps (Linux) + if: runner.os == 'Linux' + run: | + sudo apt-get update + # mesa-vulkan-drivers supplies the lavapipe software Vulkan ICD. The lens's + # offscreen render gate asks wgpu for an adapter, and a runner with no ICD has + # none — the gate would panic rather than check anything. The all-black / + # tofu frame regression it catches (see Cargo.toml on default_fonts) is + # platform-independent and worth running here, so give it an adapter. + sudo apt-get install -y libxkbcommon-dev libgl1-mesa-dev libwayland-dev \ + libx11-dev libxcursor-dev libxrandr-dev libxi-dev libxcb1-dev pkg-config \ + mesa-vulkan-drivers + - uses: dtolnay/rust-toolchain@1.96.0 + with: + components: clippy, rustfmt + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + with: + cache-on-failure: true + - name: Build lens (compiles + links the platform picker) + working-directory: lens + run: cargo build --all-targets + # Gate the lens's own tests, not just that it compiles. Without this the + # clipboard privacy/caption tests and the offscreen render gate are local-only, + # so a regression in them reaches main green. + - name: Test lens + working-directory: lens + run: cargo test + - name: Clippy lens + working-directory: lens + run: cargo clippy --all-targets -- -D warnings + - name: Rustfmt lens + working-directory: lens + run: cargo fmt --check + msrv: + # timeglyph-core declares `rust-version = "1.75"` — a low floor downstreams + # (FOUNDATION crates, published low-MSRV parsers) pin against. An MSRV nobody + # enforces is an unbacked promise, so this job is what makes the number true. + # Raising the floor is near-breaking; do it deliberately, in both places the + # assert below names. + # + # It builds core/ COPIED OUT of the repo, and that is load-bearing rather + # than incidental. Two things in the tree would otherwise decide the outcome + # instead of the crate: the workspace Cargo.lock is version 4, which cargo + # 1.75 refuses to parse at all (it fails before ever reading core/), and + # rust-toolchain.toml pins 1.96.0, which would override the toolchain this + # job just installed. Outside the repo the crate is its own workspace with + # its own lockfile — exactly how a crates.io consumer builds it. Do NOT + # "simplify" this by downgrading or deleting the committed v4 lockfile. + name: MSRV 1.75 (timeglyph-core) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: dtolnay/rust-toolchain@1.75.0 + - name: Assert the declared MSRV is the one this job checks + run: | + declared=$(grep -m1 '^rust-version' core/Cargo.toml | cut -d'"' -f2) + if [ "$declared" != "1.75" ]; then + echo "core/Cargo.toml declares rust-version = \"$declared\", but this job installs 1.75.0." + echo "The two must move together, or the floor stops being verified. Update both." + exit 1 + fi + - name: Check + test timeglyph-core on its declared MSRV, standalone + run: | + dest="$RUNNER_TEMP/timeglyph-core-msrv" + cp -R core "$dest" + cd "$dest" + # `cargo +1.75.0`, not a bare `cargo`: the explicit +toolchain sits at + # the top of rustup's precedence chain, above RUSTUP_TOOLCHAIN, a + # directory override and any rust-toolchain.toml. A bare `cargo` here + # is silently hostage to ambient state — verified locally, where an + # exported RUSTUP_TOOLCHAIN=1.96.0 beat `rustup override set 1.75.0` + # and compiled the crate on 1.96 while the job looked green. + # + # Then assert the compiler rather than merely logging it. If the + # toolchain ever fails to resolve as intended, a newer cargo would + # build this crate happily and the job would pass having verified + # nothing — the vacuous-gate shape, same as a coverage report that + # contains no records for the file it claims to cover. + version=$(cargo +1.75.0 --version) + echo "$version" + case "$version" in + "cargo 1.75.0"*) ;; + *) echo "Expected cargo 1.75.0, got: $version — the MSRV was not actually exercised."; exit 1 ;; + esac + cargo +1.75.0 check --all-targets + cargo +1.75.0 test + - name: Assert zero dependencies + # The zero-dependency promise is what lets a downstream link this crate + # for free; enforce it rather than trust the manifest comment. The + # lockfile cargo just resolved must name exactly one package — itself. + run: | + dest="$RUNNER_TEMP/timeglyph-core-msrv" + count=$(grep -c '^\[\[package\]\]' "$dest/Cargo.lock") + if [ "$count" != "1" ]; then + echo "timeglyph-core resolved $count packages; it must resolve exactly 1 (itself):" + grep '^name = ' "$dest/Cargo.lock" + exit 1 + fi + freshness: + # Advisory dependency-freshness gate: fails if the committed Cargo.lock is + # stale relative to the requirements (see CLAUDE.md "Dependency Freshness"). + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + # BOTH locks. `lens/` is excluded from the workspace and carries its own + # Cargo.lock, which this job never looked at -- so it drifted while the + # check reported on the root alone and read as covering the repo. A gate + # that inspects one of two artifacts is not a gate on the repo. + - run: cargo update --locked + - run: cargo update --locked --manifest-path lens/Cargo.toml + lean-build: + # Gate: `cargo add timeglyph --no-default-features` must NOT pull in clap, + # csv, or tracing-subscriber. Those are CLI-only deps; a library consumer + # doing `default-features = false` should get a lean build with only the + # core engine deps (jiff, thiserror, hex, serde, serde_json, tracing facade). + # Enforced now that clap/csv/tracing-subscriber sit behind the `cli`/`csv` + # features (a lean library build carries none of them). + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: dtolnay/rust-toolchain@1.96.0 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + with: + cache-on-failure: true + - name: Lean lib build (no default features) + run: cargo build --no-default-features + - name: Assert clap / csv / tracing-subscriber absent from lean tree + run: | + cargo tree --no-default-features -e no-dev --prefix none > /tmp/lean-tree.txt + UNWANTED=$(grep -E '^clap |^csv |^tracing-subscriber ' /tmp/lean-tree.txt || true) + if [ -n "$UNWANTED" ]; then + echo "FAIL: CLI-only deps present in lean (no-default-features) build:" + echo "$UNWANTED" + exit 1 + fi + wasm-build: + # Gate: the core engine must compile to wasm32 so the WASM playground and + # browser / CyberChef embeddings stay buildable. Depends on the engine being + # pure (no I/O, no clap/csv) and jiff + serde being wasm-compatible. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: dtolnay/rust-toolchain@1.96.0 + with: + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + with: + cache-on-failure: true + - name: WASM lib build (wasm32-unknown-unknown, no default features) + run: cargo build --lib --no-default-features --target wasm32-unknown-unknown + - name: WASM playground binding build (wasm32) + run: cargo build --manifest-path wasm/Cargo.toml --target wasm32-unknown-unknown + python-wheel: + # Gate: the PyO3 Python bindings build a wheel and its tests pass. Publishing + # to PyPI is a separate credentialed release step; this proves the wheel is + # always buildable and correct (plaso/Timesketch/Jupyter consume it). + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: dtolnay/rust-toolchain@1.96.0 + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 + with: + cache-on-failure: true + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.11" + - run: pip install maturin pytest + # One v* tag ships the crate AND the wheel, so the wheel version must equal + # the crate version. release-plz bumps ONLY the root crate on a release, so an + # equality *check* here can never pass on a release PR (bindings/ would still + # lag). Instead stamp the bindings from the crate — the single source of truth — + # making drift impossible by construction (this closes the 0.3.0-vs-0.4.0 gap + # without a guard release-plz can't satisfy). The same stamp runs in the wheel + # build (release.yml) so published wheels are always at the crate version. + - name: sync bindings version to the crate + run: ./bindings/python/sync-version.sh + - name: Build + test the Python wheel + working-directory: bindings/python + run: | + maturin build --release + pip install target/wheels/*.whl + pytest tests/ -q diff --git a/core/Cargo.toml b/core/Cargo.toml index 25e2613..cb458f7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" # fleet's library floor and is CI-verified. Raising it is near-breaking. rust-version = "1.75" license = "Apache-2.0" +readme = "README.md" description = "Zero-dependency epoch arithmetic: Windows FILETIME, WebKit/Chrome, Cocoa and HFS+ timestamps to Unix nanoseconds, with the not-set sentinel policy." authors = ["Albert Hui "] repository = "https://github.com/SecurityRonin/timeglyph" diff --git a/core/LICENSE b/core/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/core/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..7301a69 --- /dev/null +++ b/core/README.md @@ -0,0 +1,29 @@ +# timeglyph-core + +The integer half of [timeglyph](https://github.com/SecurityRonin/timeglyph): +turning platform epoch values into Unix nanoseconds. + +**Zero dependencies**, and the dependency count is a checked claim rather than a +README sentence — CI asserts the resolved lockfile names exactly one package. + +```toml +[dependencies] +timeglyph-core = "0.1" +``` + +Handles Windows `FILETIME`, WebKit/Chrome, Cocoa and HFS+ timestamps, along with +the not-set sentinel policy that distinguishes "the epoch" from "never set" — +a distinction that matters when the value came out of an artifact rather than a +clock. + +This crate is deliberately narrow: it does arithmetic and nothing else. The +scanning, scoring and rendering that decide *which* interpretation of some bytes +is plausible live in `timeglyph`, and are a different kind of claim. + +Its MSRV is **1.75**, verified in CI by building the crate standalone. + +--- + +[Privacy](https://securityronin.github.io/timeglyph/privacy/) · +[Terms](https://securityronin.github.io/timeglyph/terms/) · +© Security Ronin Ltd diff --git a/core/src/lib.rs b/core/src/lib.rs index 3c1ae65..3e3d78e 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -24,7 +24,7 @@ //! makes negative values ordinary — every date before 2001 is one — so only //! `0.0` and the non-finite values are rejected. //! -//! This is deliberately **not** the contract of [`timeglyph`]'s scanner, which +//! This is deliberately **not** the contract of `timeglyph`'s scanner, which //! answers a different question — "what *could* this value decode to?" — and so //! reports the 1601 instant for a zero FILETIME rather than hiding it. Neither //! is a fallback for the other; pick by what your caller does with the answer. diff --git a/src/cal.rs b/src/cal.rs index 33390fc..076c787 100644 --- a/src/cal.rs +++ b/src/cal.rs @@ -1,5 +1,5 @@ //! `timeglyph cal` — a timezone/DST/leap-aware reference calendar. This module is the pure data -//! builder: [`build_day`] computes the civil + timezone facts of a date (ISO +//! builder: [`cal::build_day`](crate::cal::build_day) computes the civil + timezone facts of a date (ISO //! week, day-of-year, Julian Day Number, Modified JD, Unix midnight, weekday, //! per-day UTC offset, DST fold/gap, and — behind the `leap` feature — leap-second //! days and GPS week) with zero I/O, so it is fully testable and serialisable. diff --git a/src/cal_color.rs b/src/cal_color.rs index cc1d84e..6baa6fa 100644 --- a/src/cal_color.rs +++ b/src/cal_color.rs @@ -28,7 +28,7 @@ pub struct Ink { } impl ColorMode { - /// Wrap `text` in the SGR for `ink` at this capability (no-op under [`Mono`]). + /// Wrap `text` in the SGR for `ink` at this capability (no-op under [`ColorMode::Mono`]). #[must_use] pub fn paint(self, ink: Ink, text: &str) -> String { let (r, g, b) = ink.rgb; @@ -40,7 +40,7 @@ impl ColorMode { } } - /// Wrap `text` in reverse video (used for `today`); no-op under [`Mono`]. + /// Wrap `text` in reverse video (used for `today`); no-op under [`ColorMode::Mono`]. #[must_use] pub fn reverse(self, text: &str) -> String { if self == ColorMode::Mono { diff --git a/src/cal_render.rs b/src/cal_render.rs index 8e1bbc1..2c0e465 100644 --- a/src/cal_render.rs +++ b/src/cal_render.rs @@ -1,4 +1,4 @@ -//! Pure renderers for [`crate::cal`] — turn a [`CalMonth`] into a monospace text +//! Pure renderers for [`crate::cal`] — turn a [`CalMonth`](crate::cal::CalMonth) into a monospace text //! calendar for the terminal. No I/O, no colour side effects here: the grid uses //! spaces only (never box-drawing characters, which misalign across fonts), one //! marker glyph per day, and an ISO-week gutter. Machine (`--json`) output is the diff --git a/src/carve.rs b/src/carve.rs index 700acee..c166116 100644 --- a/src/carve.rs +++ b/src/carve.rs @@ -1,4 +1,4 @@ -//! Bounded carve: slide [`identify_bytes`](crate::interpret::identify_bytes) over +//! Bounded carve: slide [`crate::interpret::identify_bytes`] over //! a blob and report scored hits per offset — the validatable core of "find //! timestamps in raw bytes" (a malware config blob, a hex selection, one record). //! @@ -27,7 +27,7 @@ const MAX_CARVE_BYTES: usize = 4096; /// Slide [`identify_bytes`] over every offset of `bytes` and return each reading /// whose score ≥ `min_score` and whose instant falls in `window` (nanoseconds, /// `[lo, hi)`, if given), tagged with its offset. Sorted by offset, then score -/// descending. At most [`MAX_CARVE_BYTES`] of input is swept. +/// descending. At most `MAX_CARVE_BYTES` of input is swept. #[must_use] pub fn carve(bytes: &[u8], min_score: f64, window: Option<(i128, i128)>) -> Vec { let n = bytes.len().min(MAX_CARVE_BYTES); diff --git a/src/holiday.rs b/src/holiday.rs index 2cf22a5..2fc5197 100644 --- a/src/holiday.rs +++ b/src/holiday.rs @@ -12,7 +12,7 @@ //! the covered data", not "provably an ordinary day". //! //! Cost: the ~1.5 MB blob is inflated and parsed once, lazily, on the first -//! lookup (an [`OnceLock`]); the parsed table is then held for the process +//! lookup (a [`OnceLock`](std::sync::OnceLock)); the parsed table is then held for the process //! lifetime (tens of MB). Both are opt-in with the feature. use std::collections::HashMap; @@ -43,6 +43,10 @@ fn table() -> &'static Table { /// runtime failure means a packaging regression — logged at error level, never a /// silent empty table that reads like "no holidays". fn load_or_empty(what: &str, decoded: Result) -> T { + // cov:unreachable: the embedded blobs are decoded and validated in CI, so + // this arm cannot run for a build that shipped. Kept — and logged at error + // level rather than returning a silent empty table — so a packaging + // regression announces itself instead of reading as "no holidays". decoded.unwrap_or_else(|e| { tracing::error!(table = what, error = %e, "embedded holiday data failed to decode; degrading to empty"); T::default() diff --git a/src/interpret.rs b/src/interpret.rs index 0ab1679..bc27f4f 100644 --- a/src/interpret.rs +++ b/src/interpret.rs @@ -1660,7 +1660,7 @@ fn parse_asn1_utctime(s: &str) -> Option<(PosixNs, bool)> { /// A per-format spec card generated from the registry — epoch, tick unit, /// timezone/leap semantics, plausible range, known value sentinels, and the -/// citation. Registry-derived (a projection of the same [`Format`](crate::Format) +/// citation. Registry-derived (a projection of the same [`crate::Format`] /// the engine decodes with), so it never drifts from the decoder. `None` for an /// unknown format id. #[must_use] diff --git a/src/lunisolar.rs b/src/lunisolar.rs index 846c421..ea1eeec 100644 --- a/src/lunisolar.rs +++ b/src/lunisolar.rs @@ -5,7 +5,7 @@ //! conversion is **convention-relative**: a UTC instant maps to a lunisolar date //! only once a *reference meridian* is fixed (China uses UTC+8; Vietnam UTC+7; //! Korea UTC+9), because the calendar assigns civil DAYS at that meridian. So -//! [`render`] REQUIRES a [`RenderZone`]. The optional `longitude` corrects the +//! [`render`](crate::lunisolar::render) REQUIRES a [`RenderZone`]. The optional `longitude` corrects the //! HOUR pillar to local mean solar time (真太陽時); the equation of time is NOT //! applied (stated in the reading's assumptions). //! diff --git a/src/mcp.rs b/src/mcp.rs index ca8a9d4..032775a 100644 --- a/src/mcp.rs +++ b/src/mcp.rs @@ -2,7 +2,7 @@ //! as tools — `identify`, `decode`, `explain` — so an LLM-driven DFIR workflow //! gets a cited, reproducible reading instead of a hallucinated epoch conversion //! (LLMs are reliably wrong at FILETIME↔Unix arithmetic). The handler is a pure -//! function; the `mcp` subcommand is a thin stdin→[`handle`]→stdout loop over it. +//! function; the `mcp` subcommand is a thin stdin→[`handle`](crate::mcp::handle)→stdout loop over it. use serde_json::{json, Value}; diff --git a/src/registry.rs b/src/registry.rs index 554afc5..b3cff82 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -7,7 +7,7 @@ //! the 16 packed bit-field layouts — FAT/DOS, exFAT, DTTM, BitDate/BitDec, BCD/GSM //! semi-octet, Motorola, Symantec, DVR, NS40/NS40LE, LogTime, Nokia LE, SQL Server //! — and builds [`FORMATS`] by wrapping each catalog entry in an engine [`Format`] -//! with the matching [`crate::PackedCodec`]. The SYSTEMTIME packed and +//! with the matching `PackedCodec`. The SYSTEMTIME packed and //! ULID/UUIDv1/RFC-2822/EXIF string forms live in `interpret.rs`; the leap-aware //! GPS/NTP/TAI scales in `leap.rs`. Each packed codec is cross-validated against //! the MIT `time-decode` oracle (tests/oracle.rs, tests/catalog.rs), and diff --git a/src/scan.rs b/src/scan.rs index 3430895..3a74410 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -1,7 +1,7 @@ //! Scan arbitrary text for timestamp candidates and decode each into ranked -//! readings. Three extractors — long digit runs ([`scan_numbers`]), -//! self-describing datetime strings ([`datetime_candidates`]), and raw-hex tokens -//! ([`hex_candidates`]) — feed [`interpret`](crate::interpret); [`inspect_text`] +//! readings. Three extractors — long digit runs ([`scan_numbers`](crate::scan::scan_numbers)), +//! self-describing datetime strings (`datetime_candidates`), and raw-hex tokens +//! (`hex_candidates`) — feed [`crate::interpret`]; [`inspect_text`](crate::scan::inspect_text) //! ties them together. Pure and GUI-free: it powers both the CLI `scan` command //! and the timeglyph-lens overlay. @@ -310,7 +310,7 @@ fn string_readings_opts( /// Candidate datetime-string substrings of `text`: the whole text, each line, /// and each whitespace token (deduped, >= 8 chars). Pure numeric runs are -/// excluded — those are the integer path's job ([`scan_numbers`]). +/// excluded — those are the integer path's job ([`scan_numbers`](crate::scan::scan_numbers)). fn datetime_candidates(text: &str) -> Vec { let mut seen = std::collections::BTreeSet::new(); let mut out = Vec::new(); From 7ec2c8f4963c90603ea03e5db200fee61d38a2c2 Mon Sep 17 00:00:00 2001 From: Albert Hui Date: Thu, 20 Aug 2026 22:12:03 +0000 Subject: [PATCH 2/2] fix(ci): declare toolchain components, and refresh the lens lockfile `ci / Format` and `ci / Clippy` failed with error: 'cargo-fmt' is not installed for the toolchain '1.96.0' which reads as a broken workflow and is a missing declaration here. `rust-toolchain.toml` OVERRIDES a workflow asking for the `stable` channel -- a channel ref loses to the toml -- so cargo ran the pinned 1.96.0 while the workflow had installed rustfmt and clippy for `stable`. The repo's own jobs never hit this because they pinned `dtolnay/rust-toolchain@1.96.0` explicitly and named the components per job. Every other migrated repo declares `components` in the toml; this one did not. Now it does, which is the fleet standard and fixes it at the source rather than in the caller workflow. Also refreshes `lens/Cargo.lock` (either 1.17 -> 1.18), which had drifted since this morning. `freshness` checks both lockfiles now, so it reported it. Not fixed, because it is neither new nor mine: `lens/tests/fonts.rs` fails on this machine, and equally on origin/main. It asserts every UI symbol has a glyph in some loaded fallback font, which depends on the host's installed fonts; the `Lens build` job passes on all three CI runners. --- lens/Cargo.lock | 4 ++-- rust-toolchain.toml | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lens/Cargo.lock b/lens/Cargo.lock index 72b7511..4b60953 100644 --- a/lens/Cargo.lock +++ b/lens/Cargo.lock @@ -1302,9 +1302,9 @@ dependencies = [ [[package]] name = "either" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "emath" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 0f87b44..8f8aeea 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,8 @@ [toolchain] channel = "1.96.0" +# Required, not optional. `rust-toolchain.toml` OVERRIDES a workflow that asks +# for the `stable` channel -- a channel ref loses to the toml -- so cargo runs +# this pinned toolchain while the workflow installed rustfmt and clippy for +# `stable`. The result is `'cargo-fmt' is not installed for the toolchain +# '1.96.0'`, which reads as a broken workflow and is a missing declaration here. +components = ["clippy", "rustfmt"]