diff --git a/.github/settings/actions-permissions.json b/.github/settings/actions-permissions.json new file mode 100644 index 0000000..3a3cd32 --- /dev/null +++ b/.github/settings/actions-permissions.json @@ -0,0 +1,5 @@ +{ + "enabled": true, + "allowed_actions": "selected", + "sha_pinning_required": true +} diff --git a/.github/settings/claude-environment.json b/.github/settings/claude-environment.json new file mode 100644 index 0000000..b2dd54b --- /dev/null +++ b/.github/settings/claude-environment.json @@ -0,0 +1,7 @@ +{ + "wait_timer": 0, + "prevent_self_review": false, + "reviewers": [{"type": "User", "id": 220209011}], + "can_admins_bypass": false, + "deployment_branch_policy": null +} diff --git a/.github/settings/main-ruleset.json b/.github/settings/main-ruleset.json index e6afb0f..4c0f9bc 100644 --- a/.github/settings/main-ruleset.json +++ b/.github/settings/main-ruleset.json @@ -1,7 +1,7 @@ { "name": "Main review and verified checks", "target": "branch", - "enforcement": "disabled", + "enforcement": "active", "bypass_actors": [], "conditions": {"ref_name": {"include": ["refs/heads/main"], "exclude": []}}, "rules": [ diff --git a/.github/settings/release-environment.json b/.github/settings/release-environment.json index b538f96..0653ec1 100644 --- a/.github/settings/release-environment.json +++ b/.github/settings/release-environment.json @@ -2,5 +2,6 @@ "wait_timer": 0, "prevent_self_review": false, "reviewers": [{"type": "User", "id": 220209011}], + "can_admins_bypass": true, "deployment_branch_policy": {"protected_branches": false, "custom_branch_policies": true} } diff --git a/.github/settings/selected-actions.json b/.github/settings/selected-actions.json index 6e8c9ce..2dd3e1c 100644 --- a/.github/settings/selected-actions.json +++ b/.github/settings/selected-actions.json @@ -1,5 +1,9 @@ { "github_owned_allowed": true, "verified_allowed": false, - "patterns_allowed": ["SonarSource/sonarqube-scan-action@*"] + "patterns_allowed": [ + "SonarSource/sonarqube-scan-action@*", + "anthropics/claude-code-action@*", + "oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d17cf2..1ac7c76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Fixed importing a document whose parent directory is a symlink. Workspace-root symlink rejection is unchanged. - A tracker that cannot be read now degrades to a package-only view with a warning instead of blanking the dashboard, and tracker writes are disabled while it is unreadable. - Fixed tracker status changes binding colliding package IDs to the wrong application directory. +- Fixed a failed first tracker status action leaving an unusable database; correcting the package now allows a retry without losing status or history atomicity. - Quitting with unsaved master resume edits can now be completed via Save, and `restore-cleanup` reports what was actually restored. - Fixed the SBOM export treating a pending `202` report as a failure and a delivered but invalid report as pending; retries stay bounded. - CI whitespace and conflict-marker checks now inspect the committed range for the triggering event, including merge resolutions, instead of an always-empty working-tree diff. diff --git a/Sources/NavCenterCore/TrackerStore.swift b/Sources/NavCenterCore/TrackerStore.swift index 4d17b63..102771d 100644 --- a/Sources/NavCenterCore/TrackerStore.swift +++ b/Sources/NavCenterCore/TrackerStore.swift @@ -62,8 +62,10 @@ public final class TrackerStore { let existed = SQLiteSupport.exists(dbPath) try PathSafety.createDirectory(dbPath.deletingLastPathComponent(), inside: repoRoot, label: "tracking directory") let connection = try SQLiteSupport.Connection(dbPath: dbPath, repoRoot: repoRoot, writable: true, create: !existed) + // Keep a valid empty tracker if the first status action fails. Rolling + // schema creation back with that action leaves a file that cannot retry. + if !existed { try connection.transaction { try connection.createSchema() } } let result: TrackerStatusUpdateResult = try connection.transaction { - if !existed { try connection.createSchema() } try connection.validateSchema() let applicationDir = "applications/" + packageName let expectedID = trackerID(packageName: packageName) diff --git a/Tests/NavCenterTests/CoreDataIntegrityTests.swift b/Tests/NavCenterTests/CoreDataIntegrityTests.swift index ed43f05..f7e8bb0 100644 --- a/Tests/NavCenterTests/CoreDataIntegrityTests.swift +++ b/Tests/NavCenterTests/CoreDataIntegrityTests.swift @@ -52,6 +52,42 @@ final class CoreDataIntegrityTests: XCTestCase { XCTAssertEqual(try TrackerStore(repoRoot: root).loadRows().count, 1) } + func testFailedFirstStatusActionKeepsInitializedTrackerAndCanRetry() throws { + let posting = package.appendingPathComponent("posting.md") + let invalidPosting = Data([0xFF, 0xFE]) + try invalidPosting.write(to: posting) + XCTAssertFalse(SQLiteSupport.exists(database)) + + XCTAssertThrowsError(try status()) { error in + XCTAssertTrue(error.localizedDescription.contains("not UTF-8"), error.localizedDescription) + } + + XCTAssertEqual(try Data(contentsOf: posting), invalidPosting) + XCTAssertTrue(try TrackerStore(repoRoot: root).loadRows().isEmpty) + XCTAssertTrue(try TrackerStore.queryRows(repoRoot: root, dbPath: database, sql: "select * from status_events;").isEmpty) + try makePackage(packageName) + + let result = try status(.interview) + XCTAssertEqual(result.oldStatus, "") + XCTAssertEqual(result.newStatus, "Interview") + XCTAssertTrue(result.warnings.isEmpty) + XCTAssertEqual(try TrackerStore(repoRoot: root).loadRows().map(\.status), ["Interview"]) + let events = try TrackerStore.queryRows(repoRoot: root, dbPath: database, sql: "select * from status_events;") + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first?["old_status"] as? String, "") + XCTAssertEqual(events.first?["new_status"] as? String, "Interview") + } + + func testEmptyExistingTrackerIsNotInitialized() throws { + try Data().write(to: database) + + XCTAssertThrowsError(try status()) { error in + XCTAssertTrue(error.localizedDescription.contains("missing required columns"), error.localizedDescription) + } + + XCTAssertEqual(try Data(contentsOf: database), Data()) + } + func testStatusChangeRefusesTrackerIDCollisionWithoutMutatingSibling() throws { let first = "2099-01-01_A-B_Role" let second = "2099-01-01_A_B_Role" diff --git a/docs/SETUP.md b/docs/SETUP.md index 9474f2a..aadd21f 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -1,98 +1,63 @@ # Repository and native tooling setup -Status recorded 2026-09-16 UTC. This is the living setup plan; update evidence and remaining gates as work proceeds. Configuration, executed validation, and release acceptance are separate states. [Operating runbook](TOOLING.md) · [Testing](TESTING.md) · [Architecture](ARCHITECTURE.md). +Status recorded 2026-09-22 UTC. This is the living setup record. Configuration, executed validation, and release acceptance are separate states. [Operating runbook](TOOLING.md) · [Testing](TESTING.md) · [Architecture](ARCHITECTURE.md). ## Current outcome -Repository controls were changed and read back on GitHub. Documentation, pinned tool provisioning, upgraded CI, Sonar report preparation, ownership and templates are prepared locally. No source implementation, inherited tests, or vendor snapshot files were changed. Nothing has been committed, pushed, merged, released, submitted to Apple, or uploaded to Sonar by this setup task. - -The worktree inherited substantial unpublished hardening. Its new CI relies on inherited test/release files absent from committed main. A working tooling PR cannot simply publish the whole tree without also publishing that implementation. The owner must decide whether to review/commit that baseline first or explicitly authorize a combined ready-for-review PR. Until then, setup remains a reviewable local delta. - -## Phase 0 — baseline and toolchain - -- [x] Confirm detached worktree at `0555cb0483f98de44e6c32a1a1cdd270d50a1abf`, matching original main HEAD. -- [x] Inventory 91 files: 38 tracked modifications and 25 untracked files. Original/worktree file bytes matched. Preserve original checkout; retain separate baseline and setup delta evidence. -- [x] Read repository instructions, package/source/test/release contracts, vendor manifest, tool versions and live GitHub settings. -- [x] Diagnose local tools: Command Line Tools Swift 6.4 is selected; Xcode 27.0 / 27A266a exists but its license is unaccepted. Do not accept it on the owner's behalf. -- [x] Select compatible CI: `macos-15`, explicit Xcode 26.3 / Swift 6.2.3; retain macOS 13 as deployment minimum, not verified support evidence. -- [x] Build the CLI with installed Command Line Tools using temporary build/cache directories; all four synthetic CLI tests passed. -- [ ] Owner completes Xcode license/first-launch setup. Full native app/XCTest, debug/release, sanitizers and actual coverage remain unverified in this task. The selected CI Xcode version is not installed locally. - -## Phase 1 — repository management - -- [x] Version shared `AGENTS.md`; ignore only personal `AGENTS.local.md` guidance. -- [x] Add architecture/testing/tooling docs and this checklist; mark the old product plan historical. -- [x] Add issue forms, PR template and CODEOWNERS using verified repository administrator `@austinkennethtucker`. -- [x] Replace vague security reporting instructions with the already-enabled GitHub private advisory route. -- [x] Inventory dependency/license boundaries; preserve the ATS snapshot's unresolved standalone MIT attribution notice. -- [ ] Publish the intended documentation/configuration revision after the inherited-baseline decision; local files are not yet effective repository policy on main. - -## Phase 2 — CI and dependencies - -- [x] Adapt existing CI/release workflows; retain release regressions, CLI behavior, coverage, ASAN and TSAN checks. -- [x] Put current-source/history Gitleaks and workflow checks before expensive native compilation. Use exact versions, full action SHAs and verified binary archive checksums. -- [x] Add official swift-format and focused nonduplicative SwiftLint as visible advisory baselines. Tests/builds/sanitizers remain blocking. -- [x] Validate genuine native report structure/paths/counts and retain reports/skips in explicit artifacts. No synthetic coverage is used as product evidence. -- [x] Add weekly Actions Dependabot; explicitly exclude bot rewrites of the frozen npm vendor snapshot. SwiftPM has no third-party dependencies yet. -- [x] Enable repository Dependabot alerts/security updates; keep existing CodeQL default setup for Swift/Actions, secret scanning and push protection. -- [ ] Execute the updated workflows on the exact intended hosted revision and record stable check names, provider, results and URLs. -- [ ] Obtain pinned-Xcode lint/coverage baselines and decide a scoped adoption change. The local formatter baseline contains 1,640 findings; no formatting rewrite was made. - -## Phase 3 — SonarQube Cloud - -- [x] Prepare a separate optional main-only reporting workflow, exact CI artifact binding, source/report allowlists, report hashes and path checks. -- [x] Create a `sonar` GitHub environment restricted to main. No token has been entered and `SONAR_ENABLED` remains unset. -- [x] Confirm the documented free OSS/EU path and Swift compatibility; do not select a paid trial or assume scoped OSS tokens. -- [ ] Owner signs in, personally accepts any terms, and supplies the actual existing/new OSS organization selection. The login tab is prepared for handoff. -- [ ] Install/authorize the SonarQubeCloud GitHub app for **nav-center only**, import the public project, disable automatic analysis and automatic project import, and read back settings. -- [ ] Store an expiring `SONAR_TOKEN` through secure UI/CLI in this repo's `sonar` environment. Record its real user-derived scope. Set verified organization/project variables. -- [ ] Analyze a vetted, successful native main revision; verify server-side file counts, coverage totals/paths, external findings and analyzer compatibility against native reports. -- [ ] Record explicit main baseline SHA/date, calibrated quality gate/new-code definition and representative changed-code results. Enable PR gating only after secure PR analysis and actual check behavior exist. Sonar remains nonrequired during calibration and does not replace tests or CodeQL. - -## Phase 4 — enforcement and release evidence - -- [x] Change workflow token defaults to read-only and disable workflow PR creation/approval. -- [x] Restrict Actions to GitHub-owned actions plus the approved Sonar scan action; require maintainer approval for all external-fork runs. -- [x] Create `release` environment: reviewer `austinkennethtucker`, main-only branch policy, self-review allowed for solo operation. Administrator bypass remains enabled by GitHub; no bot bypass or AI approval was configured. -- [x] Prepare a disabled main ruleset with PR/review-thread/check requirements, stale-review dismissal, no bypass actors, and no independent-review count that would lock out a solo maintainer. -- [x] Verify GitHub's SPDX endpoint and prepare source inventory retention with release evidence; document its limits and future final-artifact attestation. -- [ ] After successful candidate checks, activate the ruleset using the observed GitHub Actions names/provider and verify effective branch behavior. Main remains unprotected now. -- [ ] Enable full-SHA repository enforcement after the pinned workflows are published; current committed workflows still use floating major tags. -- [ ] Bind the published release workflow to the configured environment. Environment creation alone does not protect the existing legacy workflow. -- [ ] Confirm additional human owners if independent review is desired. Add signing credentials only through secure environment secrets, and obtain separate authorization before an actual release/signing/notarization run. -- [ ] Resolve vendor license notice, signed candidate, provenance, minimum-OS/architecture, GUI/accessibility, authenticated Codex and clean-device release gates. This setup does not establish release readiness. - -## Phase 5 — optional independent review - -- [x] Keep fresh-session Codex review as the default. Document a repository-only CodeRabbit OSS pilot and current eligibility/rate-limit caveats in the runbook. -- [ ] Only if elected after the core setup works: connect that single reviewer, measure unique findings/false positives/latency/cost, and retain human merge authority. No CodeRabbit installation or paid usage was initiated. +The previously unpublished hardening and tooling baseline is on main through [PR #2](https://github.com/subdepthtech/nav-center/pull/2) and [PR #3](https://github.com/subdepthtech/nav-center/pull/3). The September 16 publication and local Xcode-license blockers are no longer current. The development closeout has explicit human authorization to review, fix, commit, push and merge its PRs; this does not authorize a binary release, distribution or Apple submission. + +Main protection and full-SHA Actions enforcement are active and have been read back. Claude's repository allowlist startup failure was corrected, including its nested Bun action; a subsequent human-gated review run completed successfully. Sonar onboarding remains optional and deferred to the account owner. + +[PR #4](https://github.com/subdepthtech/nav-center/pull/4) merged the exact-directory tracker correction. [PR #5](https://github.com/subdepthtech/nav-center/pull/5) updates the pinned Claude and Sonar actions. [PR #6](https://github.com/subdepthtech/nav-center/pull/6) supplies recoverable first-use tracker initialization and this closeout record. Evidence below identifies the tested source candidates; final integration is checked again on the merged main revision, with CI results retained on that revision. No application feature, integration or release gate is accepted merely because its setup is present. + +## Repository controls + +The [September 22 settings readback](setup-evidence/github-settings-2026-09-22.json) contains configuration metadata only. The [September 16 snapshot](setup-evidence/github-settings-2026-09-16.json) and [preservation baseline](setup-evidence/preservation-baseline.json) remain historical evidence. + +| Control | Verified state | +| --- | --- | +| Main ruleset `23822967` | Active; PR required, review threads resolved, branch current with main, deletion and force-push prohibited; no bypass actors | +| Required checks | **Repository checks** and **Build, test and release contracts**, both bound to GitHub Actions app ID `15368` | +| Reviews | Stale approvals dismissed; zero required independent approvals avoids locking out the solo maintainer; human merge authorization remains required by project policy | +| Actions policy | GitHub-owned actions plus Sonar scan, Claude Code, and the exact nested Bun action; full commit SHAs required; other verified publishers are not implicitly allowed | +| Workflow token | Read-only defaults; workflow PR creation/approval disabled; all external-contributor fork runs require approval | +| Claude environment | Human reviewer `austinkennethtucker`; self-review allowed, administrator bypass disabled, PR branches allowed | +| Release environment | Same human reviewer; main only, self-review allowed; existing administrator bypass remains enabled and is not release authorization | +| Security | Secret scanning, push protection and Dependabot security updates enabled; existing CodeQL extended default setup covers Actions, Python and Swift | +| Sonar | Main-only environment exists; no repository variables or Sonar environment token configured; workflow remains off and nonrequired | + +The published manual [Beta Release workflow](../.github/workflows/beta-release.yml) uses the `release` environment and restricts execution to main. Repository and release-environment secret-name inventories contain no Apple signing credentials. No credentials, organization-wide access or third-party service terms were added for this closeout. ## Validation evidence -| Check | Observed result in this task | +| Scope | Observed evidence | | --- | --- | -| Preservation | Original baseline content unchanged; inherited application source, Swift tests, Python release/CLI tests and vendor files unchanged | -| Gitleaks 8.30.1 | No matches in isolated current intended source; no matches in all nine local Git commits | -| actionlint 1.7.12 | Passed all three prepared workflows | -| zizmor 1.30.1, offline auditor mode | Passed with one documented, narrowly scoped trusted-main `workflow_run` exception; no remaining findings | -| Shell syntax / diff whitespace | Passed | -| Release-script regressions | 15 passed, using synthetic signing/build/notary tools | -| ATS snapshot | All 11 hashes/inventory matched; 23 tests passed with an explicit synthetic root | -| Tooling tests | 8 passed: report counts/path/symlink/mismatch rejection plus synthetic native-test failure propagation through all three logged pipelines | -| CLI | Temporary Swift 6.4/CLT build passed; four black-box tests passed against the resolved executable | -| Formatting | 1,640 local strict findings; advisory, no source edits; pinned-Xcode baseline still pending | -| Native app, XCTest, ASAN/TSAN, coverage | Blocked/unverified: full Xcode license/setup pending; no test result fabricated | -| SwiftLint runtime | Official archive SHA-256/version and all five configured rule IDs verified. Lint invocation failed loading sourcekitdInProc (exit 133, invalid/empty report); licensed Xcode/SourceKit execution remains blocked | -| GitHub settings | Applied and read back; snapshot linked below | -| GitHub SPDX | Current asynchronous generation/fetch API returned SPDX-2.3 and three packages; retrieval/digest metadata retained. Repository-graph snapshot, not bound to candidate SHA or a complete binary inventory | -| Hosted candidate CI / Sonar ingestion / release | Not run; no published candidate or authenticated Sonar configuration | - -The first CLI build attempt failed because the sandbox could not write the compiler cache; rerunning with task-local caches under authorized native execution passed. The first CLI test invocation used an assumed pre-6.4 output layout and did not execute the binary; after resolving the actual SwiftPM output path, all four tests passed. These setup failures were diagnosed, not treated as application defects. - -## Evidence and next owner actions - -- [GitHub settings readback](setup-evidence/github-settings-2026-09-16.json) and [preservation baseline](setup-evidence/preservation-baseline.json). -- [GitHub Actions settings](https://github.com/subdepthtech/nav-center/settings/actions), [environments](https://github.com/subdepthtech/nav-center/settings/environments), [rulesets](https://github.com/subdepthtech/nav-center/settings/rules), [private vulnerability reporting](https://github.com/subdepthtech/nav-center/security/advisories/new). -- Required input: complete Xcode's agreement/setup; sign in and choose the Sonar OSS organization (EU for a new free account); enter the token securely; decide whether inherited implementation is reviewed/committed first or may be included in a combined ready-for-review PR. - -No pending question is permission to publish inherited implementation. The prepared ruleset, Sonar activation, hosted validation and release controls must be completed in the order described above. +| Local toolchain | `/Applications/Xcode.app`, Xcode 27.0 / `27A266a`, Swift 6.4; `xcodebuild -checkFirstLaunchStatus` passes. This differs from CI's Xcode 26.3 / Swift 6.2.3 reference. | +| PR #4 exact tracker binding | Merged at `579785023b8299acaaf36b84fe1ff890cf17d403` after independent review and successful [fresh CI](https://github.com/subdepthtech/nav-center/actions/runs/35729198025) on head `01bebab4eb40a33bcb07f56c7212852211d95683`, including both sanitizers. | +| PR #5 Actions updates | Merged at `ae988d7e0c10171004b457e2e584e177dae8b5cd` after refreshing onto PR #4 main at head `7257fc9866505472c319c6bba701dde38eaf576b`; [CI](https://github.com/subdepthtech/nav-center/actions/runs/35729936348) and [CodeQL](https://github.com/subdepthtech/nav-center/actions/runs/35729934702) passed. The [Claude run](https://github.com/subdepthtech/nav-center/actions/runs/35729936263) was admitted but deliberately skipped model execution because its workflow differed from main; this preserves the action's workflow-validation boundary. The recomputed diff still contains only the reviewed action updates; official upstream pins, actionlint 1.7.12 and whitespace checks passed. | +| Claude startup recovery | [Review run 35729198022](https://github.com/subdepthtech/nav-center/actions/runs/35729198022) succeeded on PR #4 head `01bebab4eb40a33bcb07f56c7212852211d95683` after the environment approval. This verifies execution beyond the former zero-job startup failure. A fresh [conversation event](https://github.com/subdepthtech/nav-center/actions/runs/35729865630) was admitted and correctly skipped without a trigger mention; it was not an authenticated conversation test. | +| First-use tracker initialization (F2) | Fix `9f94675dec316bf1889e6d57492f79bc9d07afd8`: the new invalid-UTF8 first-action/retry regression failed against unchanged pre-fix source. Separate schema initialization keeps a valid empty tracker after a rejected action; existing invalid databases remain rejected unchanged. Independent review passed; all 48 core tests and normal unfiltered coverage discovery passed (178 tests, 3 optional skips, zero failures). [Hosted native CI](https://github.com/subdepthtech/nav-center/actions/runs/35729584599) and [CodeQL](https://github.com/subdepthtech/nav-center/actions/runs/35729581087) also passed on that source. | +| Synthetic CLI and app smoke | On application source `9f94675`: explicit disposable workspace, initialization/doctor, import, package preview/create, duplicate refusal without mutation and redacted diagnostics passed. The actual app displayed that workspace, changed the synthetic package to Submitted then Interview, retained Interview after Refresh and rendered posting/resume previews. Read-only SQLite verification found one exact-directory row and exactly two expected history events; derived Markdown matched. The task-owned app exited normally. This is a focused smoke, not full GUI/accessibility acceptance. | +| Real export smoke | On the same source, installed Pandoc 3.11, Chrome and Poppler 26.09.0 produced HTML, DOCX, PDF and both text extracts from only the synthetic resume. Expected content was verified in the outputs. Broader conversion/Unicode/layout acceptance remains separate. | +| Script and vendor contracts | Python discovery: 55 tests, 4 CLI skips without `NAVCENTERCTL`; separate CLI smoke passed. All 11 vendor manifest files and 23 synthetic vendor tests passed; shell syntax passed. Stubbed signing/notary tests do not establish Apple acceptance. | + +Hosted native CI covers debug/release builds, XCTest coverage, address/thread sanitizers, CLI behavior and synthetic release-script contracts. Its artifacts retain actual skip reasons. Formatting and focused SwiftLint are advisory during adoption; review the pinned-Xcode reports before promoting either to a required gate. Historical local formatter counts and the old SourceKit startup failure are not current release evidence. + +## Deferred Sonar onboarding + +Sonar is not a development-closeout blocker. The account owner must choose the suitable organization, personally accept any service terms, authorize the GitHub app for **nav-center only**, disable automatic analysis, and supply an expiring analysis credential through the secure environment UI/CLI. No paid trial or purchase is authorized. + +After those owner steps, follow [TOOLING.md](TOOLING.md#sonarqube-cloud-onboarding) to set verified project variables and enable the workflow. Validate actual server-side source counts, coverage, findings and quality-gate results against the exact successful native CI artifact. Keep Sonar nonrequired until its imports, baseline and secure PR-check behavior have been demonstrated. A prepared workflow or scanner exit code does not establish ingestion. + +## Next milestone: reliable, installable macOS beta + +Development checks do not complete these acceptance gates: + +- Exercise the native GUI with a disposable workspace: first-use setup, create/edit/save, tracker updates, cleanup/recovery and meaningful error states. Verify keyboard and VoiceOver accessibility. Model tests and a short launch smoke are insufficient. +- Extend the successful synthetic export smoke to representative Unicode, layout and error cases. Verify a reviewed installed ATS executable and a live signed-in Codex session with package-edit confirmation separately. Standard native tests still skip the opt-in installed Chrome and two ATS tests unless explicitly configured; the separate export smoke does not replace those tests. Preserve skipped or unavailable evidence. +- Complete the frozen ATS snapshot's missing standalone license/attribution notice before distribution. Keep its manifest and dependency boundary intact; it is not bundled or activated by default. +- Select and test the supported macOS and architecture matrix. The declared macOS 13 minimum is not established by local macOS 27 or hosted macOS 15 results. +- Under separate release authorization, configure Apple signing credentials securely and validate a candidate's nested app/CLI and outer DMG signatures, notarization/stapling, Gatekeeper behavior, source/toolchain/checksum lineage and final-artifact provenance. +- Verify a downloaded candidate on a clean device, including install, first launch, update, uninstall and data preservation. Retain results in the [public release checklist](PUBLIC_RELEASE_CHECKLIST.md); do not infer acceptance from successful build or upload. + +CodeRabbit and additional analysis services remain optional. No new product/iOS work or binary distribution is part of this closeout. diff --git a/docs/TOOLING.md b/docs/TOOLING.md index d7544b5..5410bc6 100644 --- a/docs/TOOLING.md +++ b/docs/TOOLING.md @@ -8,7 +8,7 @@ Use [SETUP.md](SETUP.md) for current completion and blockers, [TESTING.md](TESTI | --- | --- | --- | | Repository | Gitleaks 8.30.1 | Current source and full Git history, with redacted findings | | Workflows | actionlint 1.7.12; zizmor 1.30.1 auditor mode | YAML/expression/shell checks and workflow security | -| Native | Xcode 26.3, Swift 6.2.3, XCTest, SwiftPM | Full builds, tests, real coverage, CLI and sanitizers | +| Native | CI: Xcode 26.3 / Swift 6.2.3; local toolchain recorded in SETUP.md | Full builds, XCTest, real coverage, CLI and sanitizers | | Formatting | Apple's official swift-format from the selected Xcode | Read-only style baseline; advisory during adoption | | Swift lint | SwiftLint 0.65.1 | Five correctness rules; no duplicate whitespace rules; advisory baseline | | Security | Existing CodeQL default setup, secret scanning, push protection | Preserve Swift/Actions extended analysis and repository secret controls | @@ -43,13 +43,13 @@ The one zizmor exception is documented inline on Sonar's `workflow_run` trigger. CI uploads `native-quality` for 14 days, even on a failed native job when reports exist. Logs preserve real XCTest skips; integration-scope text explicitly identifies unrun Chrome/ATS/Codex/UI/signing work. Sanitizer/test/build failures fail CI. Formatting and SwiftLint failures are visibly reported as advisory outcomes during baseline calibration; a missing/invalid SwiftLint report still fails analysis-report validation. -The local initial formatter baseline was generated by installed Command Line Tools Swift 6.4, whose formatter reports version `main`; it is not the pinned CI baseline. Obtain the Xcode 26.3 baseline before enforcing style. An inherited `try!` in `Utilities.swift` is also visible to the new focused rules. Resolve or explicitly review findings in a separate scoped source change; never silently reformat inherited implementation. Promote selected lint checks to required status only after a compatible hosted baseline and a clean intended revision. +The initial September 16 formatter baseline came from Command Line Tools Swift 6.4, whose formatter reports version `main`. It is historical, not the pinned CI baseline. Use the exact successful Xcode 26.3 CI artifact before enforcing style. An inherited `try!` in `Utilities.swift` is also visible to the new focused rules. Resolve or explicitly review findings in a separate scoped source change; never silently reformat inherited implementation. Promote selected lint checks to required status only after a compatible hosted baseline and a clean intended revision. Coverage generation exports native `llvm-cov show` text and LLVM JSON. The validator rejects empty, duplicate, impossible-count, unmaintained, or external-path reports; converts verified source paths to repository-relative paths; and records source files absent from coverage. It does not invent missing coverage. Sonar consumes the text/SwiftLint JSON, not generic XML or an unrelated `.xcresult` converter. ## SonarQube Cloud onboarding -The workflow is prepared but remains off until `SONAR_ENABLED=true` is deliberately set. Use an existing suitable organization. For a new organization, choose the explicit OSS plan: public projects are free, with public branch/PR analysis; private projects are excluded. New free accounts use EU; US currently requires Enterprise. Do not start a paid trial or purchase a plan. [Plans](https://docs.sonarsource.com/sonarqube-cloud/administering-sonarcloud/managing-subscription/subscription-plans), [region constraints](https://docs.sonarsource.com/sonarqube-cloud/getting-started/choosing-your-region). +The workflow is published but remains off until `SONAR_ENABLED=true` is deliberately set. Onboarding is deferred to the account owner and does not block development closeout; no Sonar token or project variables are configured in the September 22 readback. Use an existing suitable organization. For a new organization, choose the explicit OSS plan: public projects are free, with public branch/PR analysis; private projects are excluded. New free accounts use EU; US currently requires Enterprise. Do not start a paid trial or purchase a plan. [Plans](https://docs.sonarsource.com/sonarqube-cloud/administering-sonarcloud/managing-subscription/subscription-plans), [region constraints](https://docs.sonarsource.com/sonarqube-cloud/getting-started/choosing-your-region). 1. The account owner signs in and accepts any service terms personally. Import the actual GitHub organization and select only `subdepthtech/nav-center` for the SonarQubeCloud GitHub app. Do not grant all-repository access or enable automatic project import. 2. Select the actual project, record its organization key and project key, and disable **Administration → Analysis Method → Automatic Analysis**. CI and automatic analysis must not compete; automatic analysis cannot import these reports. [Analysis modes](https://docs.sonarsource.com/sonarqube-cloud/analyzing-source-code/automatic-analysis). @@ -70,20 +70,24 @@ Record the first analyzed main SHA/date and Sonar analysis link in SETUP.md. Con Begin with the standard quality gate visible but nonrequired. After the native imports and useful findings are confirmed, define new code relative to the recorded baseline/reference branch, inspect a representative changed-code analysis, and verify that a deliberate gate failure is visible. Record the selected gate and thresholds. Only then require the actual Sonar check name observed on a tested PR; the initial main-only workflow must first be extended for secure PR analysis. Never require a check that this workflow cannot emit for PRs. Existing debt remains an explicit backlog rather than a fabricated clean baseline. [Quality-gate operation](https://docs.sonarsource.com/sonarqube-cloud/standards/managing-quality-gates/introduction-to-quality-gates). -## GitHub controls and activation +## GitHub controls and maintenance -Current repository settings are captured in [setup evidence](setup-evidence/github-settings-2026-09-16.json). Mutations used existing authenticated repository-admin access; no new organization permission or credential was granted. The Sonar action allowlist is not an app installation. No Sonar token or Apple signing secrets have been configured. +Current repository settings are captured in the [September 22 readback](setup-evidence/github-settings-2026-09-22.json); the September 16 snapshot is historical. The closeout used existing repository-admin access without adding organization-wide access or credentials. Settings payloads under [`.github/settings`](../.github/settings) describe the applied controls; changing a local JSON file does not apply it to GitHub. -Desired payloads are under [`.github/settings`](../.github/settings). Workflow token defaults are read-only, workflow PR creation/approval is off, Actions allow GitHub-owned actions plus the Sonar scan action, and all external-fork runs need maintainer approval. Existing default CodeQL is retained; do not add a duplicate advanced setup. Secret scanning and push protection remain on. Dependabot security updates are enabled; the configuration explicitly ignores npm updates inside the frozen vendor snapshot, whose security updates require manual review. +Workflow token defaults are read-only, workflow PR creation/approval is off, and all external-contributor fork runs need maintainer approval. The repository allows GitHub-owned actions, `SonarSource/sonarqube-scan-action`, `anthropics/claude-code-action`, and only the reviewed `oven-sh/setup-bun` commit `0c5077e51419868618aeaa5fe8019c62421857d6`. Full-SHA enforcement is active, including for the allowlisted actions. Other verified publishers are not implicitly permitted. When updating a composite action, inspect its nested action references as well as the outer SHA; an omitted nested action can still prevent startup. -`CODEOWNERS` routes changes to verified administrator `@austinkennethtucker`. The prepared main ruleset requires PRs, resolved review threads, current required checks, and prevents deletion/force-push. It dismisses stale approvals but requires zero independent approvals until a second reviewer is confirmed, avoiding a solo-maintainer lockout. It has no bypass actors and binds check names to GitHub Actions (app ID 15368). +The `claude` environment requires human reviewer `austinkennethtucker`, permits self-review for solo operation and has administrator bypass disabled. It allows PR branches so both review and conversation workflows can reach their approval gate. The allowlist correction was verified by [successful review run 35729198022](https://github.com/subdepthtech/nav-center/actions/runs/35729198022). Preserve this approval boundary; a gate approval does not authorize an autonomous merge or release. Claude is optional and is not a required main check. -Activation order: +The active main ruleset requires PRs, resolved review threads, and successful **Repository checks** and **Build, test and release contracts** against a branch current with main. Both check contexts are bound to GitHub Actions app ID `15368`. Deletion and force-push are prohibited; there are no bypass actors. Stale approvals are dismissed, with zero independent approvals required until a second reviewer is confirmed. `CODEOWNERS` routes review to `@austinkennethtucker`; human merge authorization remains a project requirement. -1. Resolve the inherited-work publication decision, create a ready-for-review PR, and obtain **Repository checks** and **Build, test and release contracts** on the exact candidate revision. Read back their names, provider ID, conclusions and SHA. A configuration file or old CodeQL pass is not enough. -2. After compatible pinned workflows exist, enable repository full-SHA enforcement. Verify CodeQL default setup remains operational; do not strand the published tag-based workflows. -3. Change `main-ruleset.json` enforcement from `disabled` to `active`, apply it through the repository rulesets API, and read back effective branch rules. Confirm PR/check behavior without merging an unapproved candidate. Keep Sonar optional until its separate calibration criteria are met. -4. Recheck environments before any release. `release` has the verified human reviewer and permits only main; self-review remains allowed for solo operation. GitHub's current administrator-bypass capability remains enabled and must be considered in the review model. The published legacy release workflow does not use the new environment until the prepared workflow lands. +Maintain these controls when changing workflows: + +1. Verify the exact candidate SHA, current base and required-check names/provider/conclusions. Recheck successor PRs after earlier merges; never waive a failing required check. +2. Keep action references at full commit SHAs and review their provenance and nested dependencies. Verify a real hosted run after policy or workflow changes, including continued default CodeQL operation; do not add duplicate advanced CodeQL setup. +3. Read back the effective main rules and environment settings after authorized changes. Keep Sonar optional until its separate calibration and secure PR-analysis criteria are met. +4. Before an authorized release, recheck the `release` environment's human reviewer, main-only branch policy and secure signing credentials. Self-review remains allowed and existing administrator bypass remains enabled; neither is release authorization. The published Beta Release workflow already uses this environment. + +Secret scanning, push protection and Dependabot security updates remain enabled. Default CodeQL extended analysis covers Actions, Python and Swift. Dependabot's configuration excludes npm rewrites inside the frozen vendor snapshot; updates there require explicit integrity and attribution review. The release workflow retains validated evidence for 90 days and exports GitHub's source SPDX inventory through the asynchronous generation/fetch API. The exporter waits for a bounded interval and labels the result as a current repository-graph snapshot, not an inventory bound to the candidate SHA. The deprecated synchronous endpoint retires November 13, 2026 and is not used by the workflow. This does not supply signing credentials, prove notarization, or create a distributable artifact. [GitHub SBOM API](https://docs.github.com/en/rest/dependency-graph/sboms). Attestation preparation and remaining evidence are in [RELEASE.md](RELEASE.md) and [DEPENDENCIES.md](DEPENDENCIES.md). diff --git a/docs/setup-evidence/github-settings-2026-09-22.json b/docs/setup-evidence/github-settings-2026-09-22.json new file mode 100644 index 0000000..620c25d --- /dev/null +++ b/docs/setup-evidence/github-settings-2026-09-22.json @@ -0,0 +1,224 @@ +{ + "recorded_at": "2026-09-22T12:49:09+00:00", + "repository": "subdepthtech/nav-center", + "scope": "Repository configuration readback; contains no secret values or account authentication data.", + "actions_permissions": { + "enabled": true, + "allowed_actions": "selected", + "sha_pinning_required": true + }, + "selected_actions": { + "github_owned_allowed": true, + "patterns_allowed": [ + "SonarSource/sonarqube-scan-action@*", + "anthropics/claude-code-action@*", + "oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6" + ], + "verified_allowed": false + }, + "workflow_permissions": { + "default_workflow_permissions": "read", + "can_approve_pull_request_reviews": false + }, + "fork_approval_policy": { + "approval_policy": "all_external_contributors" + }, + "main_ruleset": { + "id": 23822967, + "name": "Main review and verified checks", + "target": "branch", + "enforcement": "active", + "bypass_actors": [], + "conditions": { + "ref_name": { + "exclude": [], + "include": [ + "refs/heads/main" + ] + } + }, + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "required_reviewers": [], + "require_code_owner_review": false, + "dismissal_restriction": { + "enabled": false, + "allowed_actors": [] + }, + "require_last_push_approval": false, + "required_review_thread_resolution": true, + "require_extra_approval_for_unattributed_changes": true, + "allowed_merge_methods": [ + "merge", + "squash", + "rebase" + ] + } + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": true, + "required_status_checks": [ + { + "context": "Repository checks", + "integration_id": 15368 + }, + { + "context": "Build, test and release contracts", + "integration_id": 15368 + } + ] + } + } + ] + }, + "effective_main_rules": [ + { + "type": "deletion", + "ruleset_id": 23822967 + }, + { + "type": "non_fast_forward", + "ruleset_id": 23822967 + }, + { + "type": "pull_request", + "ruleset_id": 23822967 + }, + { + "type": "required_status_checks", + "ruleset_id": 23822967 + } + ], + "environments": { + "claude": { + "id": 22476712378, + "name": "claude", + "can_admins_bypass": false, + "deployment_branch_policy": null, + "protection_rules": [ + { + "type": "required_reviewers", + "prevent_self_review": false, + "reviewers": [ + { + "type": "User", + "id": 220209011, + "login": "austinkennethtucker" + } + ] + } + ], + "secret_names": [] + }, + "release": { + "id": 22021494237, + "name": "release", + "can_admins_bypass": true, + "deployment_branch_policy": { + "protected_branches": false, + "custom_branch_policies": true + }, + "protection_rules": [ + { + "type": "required_reviewers", + "prevent_self_review": false, + "reviewers": [ + { + "type": "User", + "id": 220209011, + "login": "austinkennethtucker" + } + ] + }, + { + "type": "branch_policy" + } + ], + "allowed_branches": [ + { + "name": "main", + "type": "branch" + } + ], + "secret_names": [] + }, + "sonar": { + "id": 22021551899, + "name": "sonar", + "can_admins_bypass": true, + "deployment_branch_policy": { + "protected_branches": false, + "custom_branch_policies": true + }, + "protection_rules": [ + { + "type": "branch_policy" + } + ], + "allowed_branches": [ + { + "name": "main", + "type": "branch" + } + ], + "secret_names": [] + } + }, + "repository_variable_names": [], + "repository_secret_names": [ + "CLAUDE_CODE_OAUTH_TOKEN" + ], + "security_and_analysis": { + "secret_scanning": { + "status": "enabled" + }, + "secret_scanning_push_protection": { + "status": "enabled" + }, + "dependabot_security_updates": { + "status": "enabled" + }, + "secret_scanning_non_provider_patterns": { + "status": "disabled" + }, + "secret_scanning_validity_checks": { + "status": "disabled" + } + }, + "codeql_default_setup": { + "state": "configured", + "languages": [ + "actions", + "python", + "swift" + ], + "query_suite": "extended", + "threat_model": "remote", + "updated_at": "2026-09-18T17:08:52Z", + "schedule": "weekly", + "runner_type": "standard", + "runner_label": "" + }, + "claude_startup_verification": { + "id": 35729198022, + "head_sha": "01bebab4eb40a33bcb07f56c7212852211d95683", + "event": "pull_request", + "status": "completed", + "conclusion": "success", + "html_url": "https://github.com/subdepthtech/nav-center/actions/runs/35729198022", + "run_attempt": 1 + } +}