@mnstry/atelier 0.2.0-alpha.11 → 0.2.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1005 -0
- package/README.md +149 -9
- package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
- package/contracts/atelier-architecture.v1.schema.json +258 -0
- package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
- package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
- package/contracts/atelier-build.v1.schema.json +1076 -0
- package/contracts/atelier-capability.v1.schema.json +1252 -0
- package/contracts/atelier-coordination.v1.schema.json +1050 -0
- package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
- package/contracts/atelier-harness.v1.schema.json +234 -0
- package/contracts/atelier-ingestion.v1.schema.json +806 -0
- package/contracts/atelier-inquiry.v1.schema.json +1772 -0
- package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
- package/contracts/atelier-interaction.v1.schema.json +553 -0
- package/contracts/atelier-judgment.v1.schema.json +690 -0
- package/contracts/atelier-knowledge.v1.schema.json +1512 -0
- package/contracts/atelier-learning.v1.schema.json +1095 -0
- package/contracts/atelier-migration.v3.schema.json +64 -0
- package/contracts/atelier-obsidian-generation-manifest.v2.schema.json +372 -0
- package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
- package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
- package/contracts/atelier-template-adoption.v1.schema.json +223 -0
- package/contracts/atelier-template-profile.v1.schema.json +1168 -0
- package/contracts/atelier-trackable.v1.schema.json +781 -0
- package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
- package/contracts/compat-baseline.json +1 -1
- package/docs/architecture.md +89 -0
- package/docs/assurance-controls.md +4 -3
- package/docs/blocks/capability-stewardship.md +17 -0
- package/docs/blocks/claims.md +16 -3
- package/docs/blocks/inquiry-harnesses.md +19 -0
- package/docs/blocks/learning-harnesses.md +17 -0
- package/docs/capability-stewardship.md +269 -0
- package/docs/coordination.md +51 -0
- package/docs/decision-practices.md +111 -0
- package/docs/decisions.md +29 -0
- package/docs/evidence-navigation.md +237 -0
- package/docs/exact-upgrades.md +3 -2
- package/docs/ingestion.md +181 -0
- package/docs/inquiry-harnesses.md +211 -0
- package/docs/install.md +80 -3
- package/docs/intake-and-guides.md +5 -0
- package/docs/integration-contract-decisions.md +24 -2
- package/docs/interaction-and-reflection.md +72 -0
- package/docs/knowledge-graph.md +20 -0
- package/docs/knowledge-health/api.md +30 -0
- package/docs/knowledge-health/assessment/api.md +96 -0
- package/docs/knowledge-health/assessment/capabilities.md +40 -0
- package/docs/knowledge-health/assessment/evaluation.md +27 -0
- package/docs/knowledge-health/assessment/facilitator.md +30 -0
- package/docs/knowledge-health/assessment/quickstart.md +61 -0
- package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
- package/docs/knowledge-health/devday/facilitator.md +16 -0
- package/docs/knowledge-health/devday/participant-response.md +22 -0
- package/docs/knowledge-health/devday/public-default.md +107 -0
- package/docs/knowledge-health/devday/quickstart.md +18 -0
- package/docs/knowledge-health/devday/support.md +22 -0
- package/docs/knowledge-health/exercise.md +17 -0
- package/docs/knowledge-health/facilitator.md +20 -0
- package/docs/knowledge-health/quickstart.md +94 -0
- package/docs/knowledge-health/readme.md +15 -0
- package/docs/knowledge-health/support.md +34 -0
- package/docs/knowledge-setup.md +248 -0
- package/docs/knowledge-workspace.md +264 -0
- package/docs/learning-harnesses.md +238 -0
- package/docs/learning.md +215 -0
- package/docs/local-services.md +242 -26
- package/docs/obsidian-contract.md +878 -36
- package/docs/obsidian-plugin.md +619 -0
- package/docs/obsidian.md +809 -64
- package/docs/optional-ingestion.md +83 -0
- package/docs/personal-workspace.md +257 -0
- package/docs/practical-judgment.md +92 -0
- package/docs/progressive-ingestion-acceptance.md +89 -0
- package/docs/progressive-ingestion.md +235 -0
- package/docs/project-command-inventory.md +1 -0
- package/docs/release-engineering.md +56 -1
- package/docs/repo-boundary-guard.md +94 -0
- package/docs/research-starter-quickstart.md +66 -0
- package/docs/research-starter.md +114 -0
- package/docs/semantic-ingestion.md +198 -0
- package/docs/semantic-operation.md +317 -0
- package/docs/skill-steward.md +6 -0
- package/docs/template-profile.md +164 -0
- package/docs/template-upgrades.md +114 -0
- package/docs/trackables.md +111 -0
- package/docs/upgrade.md +68 -3
- package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
- package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
- package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
- package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
- package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
- package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
- package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
- package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
- package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
- package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
- package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
- package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
- package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
- package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
- package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
- package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
- package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
- package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
- package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
- package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
- package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
- package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
- package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
- package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
- package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
- package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
- package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
- package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
- package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
- package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
- package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
- package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
- package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
- package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
- package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
- package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
- package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
- package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
- package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
- package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
- package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
- package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
- package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
- package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
- package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
- package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
- package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
- package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
- package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
- package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
- package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
- package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
- package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
- package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
- package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
- package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
- package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
- package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
- package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
- package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
- package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
- package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
- package/fixtures/atelier-interaction/act/valid/document.json +24 -0
- package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
- package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
- package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
- package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
- package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
- package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
- package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
- package/fixtures/atelier-judgment/case/valid/document.json +75 -0
- package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
- package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
- package/fixtures/atelier-judgment/native-decision.md +3 -0
- package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
- package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
- package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
- package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
- package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
- package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
- package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
- package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
- package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
- package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
- package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
- package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
- package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
- package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-learning/invalid/authority.json +14 -0
- package/fixtures/atelier-learning/valid/capture.json +13 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
- package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
- package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
- package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
- package/fixtures/atelier-trackable/request/valid/document.json +22 -0
- package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
- package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
- package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
- package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
- package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
- package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
- package/fixtures/capability-packages/adoption.example.json +70 -0
- package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
- package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
- package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
- package/fixtures/capability-packages/research/capability-package.json +88 -0
- package/fixtures/capability-packages/research/capability-release.json +134 -0
- package/fixtures/capability-packages/research/evaluation.md +15 -0
- package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
- package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
- package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
- package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
- package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
- package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
- package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
- package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
- package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
- package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
- package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
- package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
- package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
- package/fixtures/evidence-navigation/valid/claim.json +36 -0
- package/fixtures/evidence-navigation/valid/host.json +16 -0
- package/fixtures/evidence-navigation/valid/profile.json +16 -0
- package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
- package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
- package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
- package/fixtures/harnesses/learning-cycle.json +780 -0
- package/fixtures/inquiry/workshop.json +675 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
- package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
- package/fixtures/knowledge-health/calibration-suite.json +26 -0
- package/fixtures/knowledge-health/calibration-trial.json +37 -0
- package/fixtures/knowledge-health/corrected/checklist.md +18 -0
- package/fixtures/knowledge-health/devday/participant-record.json +19 -0
- package/fixtures/knowledge-health/expected-summary.json +27 -0
- package/fixtures/knowledge-health/workshop/README.md +20 -0
- package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
- package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
- package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
- package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
- package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
- package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
- package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
- package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
- package/fixtures/obsidian/edits/workspace.json +1 -1
- package/fixtures/obsidian/materialization/expected-full.json +21 -21
- package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
- package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
- package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
- package/fixtures/personal-workspace/overlay.json +24 -0
- package/fixtures/templates/local-library.v1.json +75 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
- package/package.json +64 -6
- package/plugins/obsidian/main.js +444 -0
- package/plugins/obsidian/manifest.json +9 -0
- package/plugins/obsidian/styles.css +22 -0
- package/skills/claude/atelier-build-harness/SKILL.md +59 -0
- package/skills/claude/atelier-coordination/SKILL.md +32 -0
- package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/claude/atelier-interaction/SKILL.md +34 -0
- package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/claude/atelier-research-harness/SKILL.md +43 -0
- package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
- package/skills/claude/atelier-trackables/SKILL.md +40 -0
- package/skills/codex/atelier-build-harness/SKILL.md +59 -0
- package/skills/codex/atelier-coordination/SKILL.md +32 -0
- package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/codex/atelier-interaction/SKILL.md +34 -0
- package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/codex/atelier-research-harness/SKILL.md +43 -0
- package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
- package/skills/codex/atelier-trackables/SKILL.md +40 -0
- package/src/architecture/catalog.mjs +355 -0
- package/src/architecture/index.mjs +93 -0
- package/src/boundary/content-rules.mjs +4 -2
- package/src/boundary/delegation-command.mjs +158 -0
- package/src/boundary/delegation.mjs +396 -0
- package/src/boundary/policy.mjs +52 -10
- package/src/build/git.mjs +18 -0
- package/src/build/index.mjs +3 -0
- package/src/build/ledger.mjs +88 -0
- package/src/capabilities/adoption.mjs +381 -0
- package/src/capabilities/evidence.mjs +140 -0
- package/src/capabilities/files.mjs +124 -0
- package/src/capabilities/index.mjs +4 -0
- package/src/capabilities/instructions.mjs +250 -0
- package/src/capabilities/package.mjs +136 -0
- package/src/cli/command-failure.mjs +36 -0
- package/src/cli/execute-command.mjs +16 -2
- package/src/cli/run.mjs +65 -9
- package/src/coauthor/store.mjs +43 -2
- package/src/collaboration/proposals.mjs +189 -0
- package/src/commands/architecture.mjs +27 -0
- package/src/commands/boundary.mjs +3 -0
- package/src/commands/capability.mjs +73 -0
- package/src/commands/enroll.mjs +3 -0
- package/src/commands/harness.mjs +79 -0
- package/src/commands/ingest.mjs +39 -0
- package/src/commands/init.mjs +4 -1
- package/src/commands/inquiry.mjs +66 -0
- package/src/commands/knowledge-semantic.mjs +128 -0
- package/src/commands/knowledge.mjs +119 -0
- package/src/commands/learn.mjs +61 -0
- package/src/commands/obsidian.mjs +832 -62
- package/src/commands/practice.mjs +24 -0
- package/src/commands/setup.mjs +22 -2
- package/src/commands/trackable.mjs +19 -0
- package/src/contracts/corpus.mjs +36 -0
- package/src/coordination/index.mjs +110 -0
- package/src/egress/forbidden-egress.mjs +2 -0
- package/src/evidence-navigation/contracts.mjs +60 -0
- package/src/evidence-navigation/evaluate.mjs +69 -0
- package/src/evidence-navigation/generation.mjs +22 -0
- package/src/evidence-navigation/index.mjs +2 -0
- package/src/evidence-navigation/local.mjs +167 -0
- package/src/evidence-navigation/schema.generated.mjs +604 -0
- package/src/evidence-navigation/types.mjs +12 -0
- package/src/export/atelier-export-contract.mjs +35 -8
- package/src/graph/enroll.mjs +248 -0
- package/src/graph/graph.mjs +17 -1
- package/src/graph/knowledge-graph.mjs +58 -8
- package/src/harnesses/contracts.mjs +41 -0
- package/src/harnesses/exchange.mjs +154 -0
- package/src/harnesses/feedback.mjs +16 -0
- package/src/harnesses/history.mjs +37 -0
- package/src/harnesses/index.mjs +4 -0
- package/src/harnesses/store.mjs +29 -0
- package/src/ingestion/contracts.mjs +41 -0
- package/src/ingestion/evaluation.mjs +119 -0
- package/src/ingestion/optional/composition/index.mjs +171 -0
- package/src/ingestion/optional/evaluation/index.mjs +219 -0
- package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
- package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
- package/src/ingestion/optional/graphify/graphify_supplier.py +933 -0
- package/src/ingestion/optional/graphify/json_bridge.py +163 -0
- package/src/ingestion/optional/graphify/query_receiving.py +209 -0
- package/src/ingestion/optional/index.mjs +9 -0
- package/src/ingestion/optional/jev/bridge.mjs +166 -0
- package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
- package/src/ingestion/optional/jev/judgments.mjs +174 -0
- package/src/ingestion/optional/jev/native-translation.mjs +32 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
- package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
- package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
- package/src/ingestion/optional/vanilla/index.mjs +218 -0
- package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
- package/src/ingestion/processors.mjs +218 -0
- package/src/ingestion/semantic.mjs +203 -0
- package/src/ingestion/store.mjs +372 -0
- package/src/inquiry/beliefs.mjs +32 -0
- package/src/inquiry/contracts.mjs +23 -0
- package/src/inquiry/index.mjs +5 -0
- package/src/inquiry/ledger.mjs +135 -0
- package/src/inquiry/lenses.mjs +15 -0
- package/src/inquiry/projection.mjs +77 -0
- package/src/inquiry/store.mjs +35 -0
- package/src/intake/read-scope.mjs +12 -0
- package/src/intake/store.mjs +69 -6
- package/src/interaction/index.mjs +91 -0
- package/src/judgment/index.mjs +82 -0
- package/src/judgment/practice-evaluation.mjs +70 -0
- package/src/judgment/practice.mjs +144 -0
- package/src/knowledge/context.mjs +217 -0
- package/src/knowledge/index.mjs +5 -0
- package/src/knowledge/ingestion.mjs +79 -0
- package/src/knowledge/intake.mjs +33 -0
- package/src/knowledge/ledger.mjs +110 -0
- package/src/knowledge/participatory/guidance.mjs +36 -0
- package/src/knowledge/participatory/guided-contribution.mjs +44 -0
- package/src/knowledge/participatory/index.mjs +2 -0
- package/src/knowledge/participatory/installed-workshop.mjs +108 -0
- package/src/knowledge/plan.mjs +67 -0
- package/src/knowledge/projection.mjs +43 -0
- package/src/knowledge/semantic-operation.mjs +567 -0
- package/src/knowledge/sessions.mjs +467 -0
- package/src/knowledge/workspace.mjs +295 -0
- package/src/knowledge-health/assessment.mjs +165 -0
- package/src/knowledge-health/capabilities.mjs +350 -0
- package/src/knowledge-health/context.mjs +62 -0
- package/src/knowledge-health/example.mjs +47 -0
- package/src/knowledge-health/index.mjs +10 -0
- package/src/knowledge-health/owner-join.mjs +88 -0
- package/src/knowledge-health/proof/common.mjs +24 -0
- package/src/knowledge-health/proof/installed-check.mjs +100 -0
- package/src/knowledge-health/proof/native-readback.mjs +37 -0
- package/src/knowledge-health/proof/rehearse.mjs +68 -0
- package/src/knowledge-health/proof/validate-participant.mjs +17 -0
- package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
- package/src/knowledge-health/proof/workshop-check.mjs +36 -0
- package/src/knowledge-health/rule-identity.mjs +44 -0
- package/src/knowledge-health/workshop-composition.mjs +19 -0
- package/src/knowledge-health/workshop-profile.mjs +176 -0
- package/src/knowledge-health/workshop.mjs +99 -0
- package/src/learning/adapters.mjs +68 -0
- package/src/learning/assistance.mjs +127 -0
- package/src/learning/contracts.mjs +65 -0
- package/src/learning/store.mjs +272 -0
- package/src/personal-workspace/index.mjs +391 -0
- package/src/personal-workspace/selection.mjs +385 -0
- package/src/project/config.mjs +8 -0
- package/src/project/durable-state.mjs +3 -1
- package/src/project/file-class.mjs +1 -1
- package/src/project/private-state.mjs +10 -1
- package/src/projection/obsidian/contracts.mjs +224 -18
- package/src/projection/obsidian/edits/apply.mjs +63 -19
- package/src/projection/obsidian/edits/regions.mjs +137 -42
- package/src/projection/obsidian/materialize/index.mjs +6 -3
- package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
- package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
- package/src/projection/obsidian/materialize/redaction.mjs +327 -0
- package/src/projection/obsidian/materialize/settings.mjs +113 -34
- package/src/projection/obsidian/personal-workspace.mjs +436 -0
- package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
- package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
- package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
- package/src/projection/obsidian/proposals/adapter.mjs +47 -8
- package/src/projection/obsidian/proposals/observation.mjs +20 -7
- package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
- package/src/projection/obsidian/publication/index.mjs +2 -1
- package/src/projection/obsidian/publication/publisher.mjs +312 -36
- package/src/projection/obsidian/publication/transport.mjs +63 -8
- package/src/projection/obsidian/publication/vault-list.mjs +200 -0
- package/src/projection/obsidian/recovery/index.mjs +4 -1
- package/src/projection/obsidian/recovery/restart.mjs +3 -1
- package/src/projection/obsidian/recovery/store.mjs +168 -8
- package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
- package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
- package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
- package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
- package/src/readiness/readiness.mjs +10 -3
- package/src/reflection/index.mjs +56 -0
- package/src/runtime/obsidian/app-capability.mjs +104 -12
- package/src/runtime/obsidian/app-production-seams.mjs +161 -42
- package/src/runtime/obsidian/app-registration.mjs +313 -0
- package/src/runtime/obsidian/app-restart.mjs +154 -0
- package/src/runtime/obsidian/engine.mjs +288 -29
- package/src/runtime/obsidian/extension-points.mjs +1 -1
- package/src/runtime/obsidian/index.mjs +15 -8
- package/src/runtime/obsidian/launch-plan.mjs +91 -0
- package/src/runtime/obsidian/lifecycle.mjs +193 -22
- package/src/runtime/obsidian/login-item.mjs +332 -0
- package/src/runtime/obsidian/machine-settings.mjs +119 -12
- package/src/runtime/obsidian/opening.mjs +369 -63
- package/src/runtime/obsidian/pending-edits.mjs +10 -0
- package/src/runtime/obsidian/pipeline.mjs +71 -3
- package/src/runtime/obsidian/plugin-channel.mjs +289 -0
- package/src/runtime/obsidian/plugin-choice.mjs +182 -0
- package/src/runtime/obsidian/plugin-drift.mjs +124 -0
- package/src/runtime/obsidian/plugin-presence.mjs +61 -0
- package/src/runtime/obsidian/process-identity.mjs +20 -10
- package/src/runtime/obsidian/project-views.mjs +282 -0
- package/src/runtime/obsidian/questions.mjs +48 -0
- package/src/runtime/obsidian/release-watch.mjs +56 -0
- package/src/runtime/obsidian/service-main.mjs +113 -17
- package/src/runtime/obsidian/service-manager-production.mjs +61 -0
- package/src/runtime/obsidian/service-managers.mjs +275 -0
- package/src/runtime/obsidian/service-record.mjs +105 -5
- package/src/runtime/obsidian/service-server.mjs +50 -12
- package/src/runtime/obsidian/service.mjs +134 -7
- package/src/runtime/obsidian/startup-adapters.mjs +59 -21
- package/src/runtime/obsidian/state-store.mjs +13 -1
- package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
- package/src/runtime/obsidian/vault-location.mjs +300 -0
- package/src/runtime/obsidian/view-counts.mjs +34 -0
- package/src/server/local-sidecar.mjs +193 -7
- package/src/server/server.mjs +45 -6
- package/src/template-bindings/project.mjs +92 -0
- package/src/templates/profile.mjs +225 -0
- package/src/trackables/domain.mjs +175 -0
- package/src/trackables/store.mjs +114 -0
- package/src/ui/knowledge-page.mjs +1388 -0
- package/src/upgrade/explanation.mjs +1 -1
- package/src/upgrade/template-participant.mjs +171 -0
- package/src/upgrade/transaction.mjs +72 -25
- package/src/upgrade/upgrade.mjs +2 -1
- package/templates/external-project-workspace/pack-lifecycle.json +6 -1
- package/templates/knowledge-workspace/.gitattributes +2 -0
- package/templates/knowledge-workspace/AGENTS.md +27 -0
- package/templates/knowledge-workspace/README.md +44 -0
- package/templates/knowledge-workspace/atelier.project.json +22 -0
- package/templates/knowledge-workspace/gitignore +6 -0
- package/templates/knowledge-workspace/knowledge-plan.json +111 -0
- package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
- package/templates/knowledge-workspace/records/forecast.md +22 -0
- package/templates/knowledge-workspace/records/inspection.md +25 -0
- package/templates/knowledge-workspace/records/telescope.md +24 -0
- package/templates/knowledge-workspace/repo-access.v1.json +9 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// Explicit selection, inventory and restore for a personal workspace's private
|
|
2
|
+
// home. Composition decides eligibility; this module records a person's choice,
|
|
3
|
+
// lists what the home retains, and restores authored inputs from an earlier
|
|
4
|
+
// generation without ever widening enrollment or bindings. It never deletes
|
|
5
|
+
// authored inputs, generations or records; it removes only its own temporary
|
|
6
|
+
// files. Every failure is a PersonalWorkspaceRefusal with a stable code and no path.
|
|
7
|
+
import fs from 'node:fs'
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
10
|
+
import { composePersonalWorkspace, loadPersonalManifest, loadPersonalOverlay, PersonalWorkspaceRefusal } from './index.mjs'
|
|
11
|
+
|
|
12
|
+
const MANIFEST_FILE = 'atelier.personal.json'
|
|
13
|
+
const OVERLAY_FILE = 'atelier.overlay.json'
|
|
14
|
+
const SELECTION_SCHEMA = 'atelier-personal-workspace-selection@v1'
|
|
15
|
+
const RESTORE_SCHEMA = 'atelier-personal-workspace-restore@v1'
|
|
16
|
+
const GENESIS = 'genesis'
|
|
17
|
+
const MAX_RECORD = 1024 * 1024
|
|
18
|
+
// The generation module bounds every generation file at eight times its input limit.
|
|
19
|
+
const MAX_GENERATION_FILE = 8 * 1024 * 1024
|
|
20
|
+
const MAX_RESTORE_RECORD = 4 * MAX_RECORD
|
|
21
|
+
const ID = /^[0-9a-f]{64}$/
|
|
22
|
+
// Every temporary name this module writes, including restore aside copies
|
|
23
|
+
// (.restore-<uuid>-aside-<key>.json).
|
|
24
|
+
const TEMPORARY = /^\.(selection|restore|validate)-[0-9a-f-]{36}(-[a-z]+)*\.json$/
|
|
25
|
+
const ASIDE = (key) => new RegExp(`^\\.restore-[0-9a-f-]{36}-aside-${key}\\.json$`)
|
|
26
|
+
const hash = (bytes) => createHash('sha256').update(bytes).digest('hex')
|
|
27
|
+
const sortObject = (v) => Array.isArray(v) ? v.map(sortObject) : v && typeof v === 'object'
|
|
28
|
+
? Object.fromEntries(Object.keys(v).sort().map((k) => [k, sortObject(v[k])])) : v
|
|
29
|
+
// The same canonical form the generation id is computed over.
|
|
30
|
+
const canonical = (v) => `${JSON.stringify(sortObject(v))}\n`
|
|
31
|
+
const refuse = (code, extra) => { throw Object.assign(new PersonalWorkspaceRefusal(code), extra ?? {}) }
|
|
32
|
+
const freeze = (v) => { if (v && typeof v === 'object') { Object.values(v).forEach(freeze); Object.freeze(v) } return v }
|
|
33
|
+
const restorePlans = new WeakSet()
|
|
34
|
+
|
|
35
|
+
// Maps any non-refusal failure to a stable code, so no raw error or path escapes.
|
|
36
|
+
function safe(fn, fallback) {
|
|
37
|
+
try { return fn() } catch (error) { if (error instanceof PersonalWorkspaceRefusal) throw error; refuse(fallback) }
|
|
38
|
+
}
|
|
39
|
+
function privateHome(personalHome) {
|
|
40
|
+
if (typeof personalHome !== 'string' || !path.isAbsolute(personalHome) || path.resolve(personalHome) !== personalHome) refuse('path-not-absolute')
|
|
41
|
+
if (process.platform === 'win32' || typeof process.getuid !== 'function') refuse('private-root-unverifiable')
|
|
42
|
+
let stat
|
|
43
|
+
try { stat = fs.lstatSync(personalHome) } catch { refuse('root-missing') }
|
|
44
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || fs.realpathSync(personalHome) !== personalHome) refuse('root-symlinked')
|
|
45
|
+
if (stat.uid !== process.getuid() || (stat.mode & 0o022) !== 0) refuse('not-private-location')
|
|
46
|
+
return personalHome
|
|
47
|
+
}
|
|
48
|
+
function readBytes(file, limit, tooLarge = 'malformed-input') {
|
|
49
|
+
const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW)
|
|
50
|
+
try {
|
|
51
|
+
const stat = fs.fstatSync(fd)
|
|
52
|
+
if (!stat.isFile()) refuse('malformed-input')
|
|
53
|
+
if (stat.size > limit) refuse(tooLarge)
|
|
54
|
+
const bytes = fs.readFileSync(fd)
|
|
55
|
+
if (bytes.length > limit) refuse(tooLarge)
|
|
56
|
+
return bytes
|
|
57
|
+
} finally { fs.closeSync(fd) }
|
|
58
|
+
}
|
|
59
|
+
const bytesOrNull = (file, limit) => { try { return readBytes(file, limit) } catch (error) { if (error.code === 'ENOENT') return null; throw error } }
|
|
60
|
+
function syncDir(dir) { const fd = fs.openSync(dir, 'r'); try { fs.fsyncSync(fd) } finally { fs.closeSync(fd) } }
|
|
61
|
+
function checkPrivateDir(dir) {
|
|
62
|
+
const stat = fs.lstatSync(dir)
|
|
63
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || stat.uid !== process.getuid() || (stat.mode & 0o022) !== 0) refuse('not-private-location')
|
|
64
|
+
return dir
|
|
65
|
+
}
|
|
66
|
+
function privateDir(personalHome, name, create) {
|
|
67
|
+
const dir = path.join(personalHome, name)
|
|
68
|
+
if (create) {
|
|
69
|
+
try { fs.mkdirSync(dir, { mode: 0o700 }); syncDir(personalHome) } catch (error) { if (error.code !== 'EEXIST') throw error }
|
|
70
|
+
} else {
|
|
71
|
+
try { fs.lstatSync(dir) } catch (error) { if (error.code === 'ENOENT') return null; throw error }
|
|
72
|
+
}
|
|
73
|
+
return checkPrivateDir(dir)
|
|
74
|
+
}
|
|
75
|
+
function writeExclusive(file, content) {
|
|
76
|
+
const fd = fs.openSync(file, 'wx', 0o600)
|
|
77
|
+
try { fs.writeFileSync(fd, content); fs.fsyncSync(fd) }
|
|
78
|
+
catch (error) { fs.closeSync(fd); fs.rmSync(file, { force: true }); throw error }
|
|
79
|
+
fs.closeSync(fd)
|
|
80
|
+
}
|
|
81
|
+
// Writes complete bytes to a temporary name, then publishes them under `final`
|
|
82
|
+
// with link, which never replaces an existing name. A crash leaves only a
|
|
83
|
+
// temporary file, which readers ignore and the inventory lists.
|
|
84
|
+
function publishExclusive(dir, final, content, kind) {
|
|
85
|
+
const temporary = path.join(dir, `.${kind}-${randomUUID()}.json`)
|
|
86
|
+
writeExclusive(temporary, content)
|
|
87
|
+
try { fs.linkSync(temporary, path.join(dir, final)) } catch (error) { fs.rmSync(temporary, { force: true }); throw error }
|
|
88
|
+
// Published. A temporary that cannot be removed now stays listed by the inventory,
|
|
89
|
+
// and a failed directory sync does not turn a published record into a failure.
|
|
90
|
+
try { fs.unlinkSync(temporary) } catch {}
|
|
91
|
+
try { syncDir(dir) } catch {}
|
|
92
|
+
}
|
|
93
|
+
// An inventory entry for a file that may be missing, oversized, not regular or
|
|
94
|
+
// unreadable: it is listed with its size and without a digest rather than
|
|
95
|
+
// failing the whole inventory.
|
|
96
|
+
function inventoryEntry(file, limit) {
|
|
97
|
+
try {
|
|
98
|
+
const bytes = readBytes(file, limit)
|
|
99
|
+
return { bytes: bytes.length, digest: hash(bytes) }
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error.code === 'ENOENT') return null
|
|
102
|
+
let size = null
|
|
103
|
+
try { size = fs.lstatSync(file).size } catch {}
|
|
104
|
+
return { bytes: size, digest: null }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const eligibility = (personalHome, generationId) => {
|
|
108
|
+
try { composePersonalWorkspace({ personalHome, generationId }); return { eligible: true, reason: null } }
|
|
109
|
+
catch (error) { if (error instanceof PersonalWorkspaceRefusal) return { eligible: false, reason: error.code }; throw error }
|
|
110
|
+
}
|
|
111
|
+
// Validates exact bytes with the module's own loader, through a private temporary copy.
|
|
112
|
+
function validateBytes(personalHome, bytes, kind) {
|
|
113
|
+
const temporary = path.join(personalHome, `.validate-${randomUUID()}.json`)
|
|
114
|
+
writeExclusive(temporary, bytes)
|
|
115
|
+
try { return (kind === 'manifest' ? loadPersonalManifest : loadPersonalOverlay)(temporary, { personalHome }) }
|
|
116
|
+
finally { fs.unlinkSync(temporary) }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// The selection history is an append-only hash chain of numbered records. It
|
|
120
|
+
// proves internal consistency only: an edited record whose successors were not
|
|
121
|
+
// re-linked, a gap, or a reordering refuses. Truncating, or consistently rewriting,
|
|
122
|
+
// any trailing run of records (up to the whole history) yields another valid chain
|
|
123
|
+
// and is detectable only by comparing the head with one the host observed earlier.
|
|
124
|
+
function readHistory(personalHome) {
|
|
125
|
+
const dir = privateDir(personalHome, 'selections', false)
|
|
126
|
+
if (!dir) return { records: [], files: [], head: GENESIS }
|
|
127
|
+
const names = fs.readdirSync(dir).filter((name) => !name.startsWith('.')).sort()
|
|
128
|
+
const records = [], files = []
|
|
129
|
+
let head = GENESIS
|
|
130
|
+
for (const [index, name] of names.entries()) {
|
|
131
|
+
if (name !== `${String(index + 1).padStart(6, '0')}.json`) refuse('selection-history-corrupt')
|
|
132
|
+
let bytes
|
|
133
|
+
try { bytes = readBytes(path.join(dir, name), MAX_RECORD, 'selection-history-corrupt') } catch { refuse('selection-history-corrupt') }
|
|
134
|
+
let record
|
|
135
|
+
try { record = JSON.parse(bytes) } catch { refuse('selection-history-corrupt') }
|
|
136
|
+
if (record?.schema !== SELECTION_SCHEMA || record.sequence !== index + 1 || record.previous !== head || !ID.test(record.generationId ?? '') ||
|
|
137
|
+
canonical(record) !== bytes.toString('utf8')) refuse('selection-history-corrupt')
|
|
138
|
+
records.push(record)
|
|
139
|
+
head = hash(bytes)
|
|
140
|
+
files.push({ name, bytes: bytes.length, digest: head })
|
|
141
|
+
}
|
|
142
|
+
return { records, files, head }
|
|
143
|
+
}
|
|
144
|
+
export function selectionConfirmDigest(options) {
|
|
145
|
+
return safe(() => {
|
|
146
|
+
const { generationId, previous } = options ?? {}
|
|
147
|
+
if (typeof generationId !== 'string' || typeof previous !== 'string') refuse('malformed-input')
|
|
148
|
+
return `sha256:${hash(canonical({ action: 'select', generationId, previous }))}`
|
|
149
|
+
}, 'malformed-input')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Records the person's explicit choice of the currently eligible generation.
|
|
153
|
+
// The caller first reads the current head and shows the person what it selects.
|
|
154
|
+
export function selectPersonalGeneration(options) {
|
|
155
|
+
return safe(() => {
|
|
156
|
+
const { personalHome, generationId, confirm } = options ?? {}
|
|
157
|
+
privateHome(personalHome)
|
|
158
|
+
if (typeof generationId !== 'string' || !ID.test(generationId)) refuse('malformed-input')
|
|
159
|
+
const { records, head } = readHistory(personalHome)
|
|
160
|
+
if (confirm !== selectionConfirmDigest({ generationId, previous: head })) refuse('confirmation-mismatch')
|
|
161
|
+
const { eligible, reason } = eligibility(personalHome, generationId)
|
|
162
|
+
if (!eligible) refuse(reason)
|
|
163
|
+
const dir = privateDir(personalHome, 'selections', true)
|
|
164
|
+
const record = { schema: SELECTION_SCHEMA, sequence: records.length + 1, previous: head, generationId }
|
|
165
|
+
const content = canonical(record)
|
|
166
|
+
try { publishExclusive(dir, `${String(record.sequence).padStart(6, '0')}.json`, content, 'selection') }
|
|
167
|
+
catch (error) { if (error.code === 'EEXIST') refuse('selection-concurrent'); throw error }
|
|
168
|
+
return freeze({ generationId, sequence: record.sequence, head: hash(content) })
|
|
169
|
+
}, 'selection-write-failed')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// The current selection and whether it is still eligible now. A selection never
|
|
173
|
+
// keeps a generation eligible after its inputs, roots or enrollment change.
|
|
174
|
+
export function readPersonalSelection(options) {
|
|
175
|
+
return safe(() => {
|
|
176
|
+
const { personalHome } = options ?? {}
|
|
177
|
+
privateHome(personalHome)
|
|
178
|
+
const { records, head } = readHistory(personalHome)
|
|
179
|
+
const current = records.at(-1) ?? null
|
|
180
|
+
if (!current) return freeze({ selected: null, head, eligible: false, reason: 'nothing-selected' })
|
|
181
|
+
return freeze({ selected: current.generationId, sequence: current.sequence, head, ...eligibility(personalHome, current.generationId) })
|
|
182
|
+
}, 'personal-home-unavailable')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Lists entries without failing the whole inventory: a concurrently removed entry
|
|
186
|
+
// is skipped; an unreadable, foreign or oversized one is reported without a digest.
|
|
187
|
+
function listFiles(dir, keep) {
|
|
188
|
+
if (!dir) return []
|
|
189
|
+
const listed = []
|
|
190
|
+
for (const name of fs.readdirSync(dir).filter(keep).sort()) {
|
|
191
|
+
try {
|
|
192
|
+
const bytes = readBytes(path.join(dir, name), MAX_RESTORE_RECORD, 'record-too-large')
|
|
193
|
+
listed.push({ name, bytes: bytes.length, digest: hash(bytes) })
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error.code === 'ENOENT') continue
|
|
196
|
+
let size = null
|
|
197
|
+
try { size = fs.lstatSync(path.join(dir, name)).size } catch {}
|
|
198
|
+
listed.push({ name, bytes: size, digest: null, unreadable: true })
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return listed
|
|
202
|
+
}
|
|
203
|
+
// A read-only inventory of what this module writes in the private home (authored
|
|
204
|
+
// inputs, generations, staging, selection and restore records, and its temporary
|
|
205
|
+
// files), for a person to review before any deletion they choose to confirm. It
|
|
206
|
+
// writes and deletes nothing.
|
|
207
|
+
export function inventoryPersonalHome(options) {
|
|
208
|
+
return safe(() => {
|
|
209
|
+
const { personalHome } = options ?? {}
|
|
210
|
+
privateHome(personalHome)
|
|
211
|
+
const authored = [MANIFEST_FILE, OVERLAY_FILE].map((name) => {
|
|
212
|
+
const entry = inventoryEntry(path.join(personalHome, name), MAX_RECORD)
|
|
213
|
+
return { name, bytes: entry?.bytes ?? null, digest: entry?.digest ?? null }
|
|
214
|
+
})
|
|
215
|
+
const root = privateDir(personalHome, 'generations', false)
|
|
216
|
+
const entries = root ? fs.readdirSync(root).sort() : []
|
|
217
|
+
const generations = entries.filter((name) => ID.test(name)).map((generationId) => {
|
|
218
|
+
const record = inventoryEntry(path.join(root, generationId, 'generation.json'), MAX_GENERATION_FILE)
|
|
219
|
+
return { generationId, recordDigest: record?.digest ?? null, ...eligibility(personalHome, generationId) }
|
|
220
|
+
})
|
|
221
|
+
const staging = entries.filter((name) => name.startsWith('.staging-'))
|
|
222
|
+
const selectionsDir = privateDir(personalHome, 'selections', false)
|
|
223
|
+
const restoresDir = privateDir(personalHome, 'restores', false)
|
|
224
|
+
let history
|
|
225
|
+
try { history = readHistory(personalHome) } catch (error) {
|
|
226
|
+
if (!(error instanceof PersonalWorkspaceRefusal)) throw error
|
|
227
|
+
history = { files: listFiles(selectionsDir, (name) => !name.startsWith('.')), head: null, corrupt: true }
|
|
228
|
+
}
|
|
229
|
+
const { files: selections, head } = history
|
|
230
|
+
const restores = listFiles(restoresDir, (name) => !name.startsWith('.'))
|
|
231
|
+
const temporary = [
|
|
232
|
+
...listFiles(personalHome, (name) => TEMPORARY.test(name)).map((file) => ({ ...file, location: '.' })),
|
|
233
|
+
...listFiles(selectionsDir, (name) => TEMPORARY.test(name)).map((file) => ({ ...file, location: 'selections' })),
|
|
234
|
+
...listFiles(restoresDir, (name) => TEMPORARY.test(name)).map((file) => ({ ...file, location: 'restores' })),
|
|
235
|
+
]
|
|
236
|
+
return freeze({ authored, generations, staging, selections: { head, records: selections, corrupt: history.corrupt === true }, restores, temporary })
|
|
237
|
+
}, 'personal-home-unavailable')
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// A generation's recorded inputs, verified against its own id: the id is the
|
|
241
|
+
// digest of the canonical inputs and this private home, so altered or moved
|
|
242
|
+
// history cannot be restored.
|
|
243
|
+
function recordedInputs(personalHome, generationId) {
|
|
244
|
+
if (typeof generationId !== 'string' || !ID.test(generationId)) refuse('malformed-input')
|
|
245
|
+
const root = privateDir(personalHome, 'generations', false)
|
|
246
|
+
if (!root) refuse('generation-missing')
|
|
247
|
+
let entry
|
|
248
|
+
try { entry = fs.lstatSync(path.join(root, generationId)) } catch { refuse('generation-missing') }
|
|
249
|
+
if (!entry.isDirectory() || entry.isSymbolicLink()) refuse('generation-corrupt')
|
|
250
|
+
let bytes
|
|
251
|
+
try { bytes = readBytes(path.join(root, generationId, 'inputs.json'), MAX_GENERATION_FILE, 'generation-inputs-too-large') }
|
|
252
|
+
catch (error) { if (error instanceof PersonalWorkspaceRefusal) throw error; refuse('generation-corrupt') }
|
|
253
|
+
if (hash(canonical({ inputsDigest: hash(bytes), personalHome })) !== generationId) refuse('generation-corrupt')
|
|
254
|
+
let inputs
|
|
255
|
+
try { inputs = JSON.parse(bytes) } catch { refuse('generation-corrupt') }
|
|
256
|
+
if (!inputs?.manifest || !inputs?.overlay || canonical(inputs) !== bytes.toString('utf8')) refuse('generation-corrupt')
|
|
257
|
+
return inputs
|
|
258
|
+
}
|
|
259
|
+
// The current authored file, read once: its exact bytes, digest and validated value.
|
|
260
|
+
function currentAuthored(personalHome, name, kind) {
|
|
261
|
+
const bytes = bytesOrNull(path.join(personalHome, name), MAX_RECORD)
|
|
262
|
+
if (bytes === null) {
|
|
263
|
+
// A restore interrupted between moving the file aside and linking its
|
|
264
|
+
// replacement leaves the name absent and the aside listed by the inventory.
|
|
265
|
+
if (fs.readdirSync(personalHome).some((entry) => ASIDE(kind).test(entry))) refuse('restore-interrupted')
|
|
266
|
+
refuse('malformed-input')
|
|
267
|
+
}
|
|
268
|
+
return { bytes, digest: hash(bytes), value: validateBytes(personalHome, bytes, kind) }
|
|
269
|
+
}
|
|
270
|
+
// A restore may narrow enrollment and bindings, never widen them: every
|
|
271
|
+
// repository the target enrolls must be enrolled now with the same root and
|
|
272
|
+
// remote, and every binding the target declares must be declared now.
|
|
273
|
+
function wideningCheck(target, current) {
|
|
274
|
+
const now = new Map(current.repos.filter((r) => r.enrolled).map((r) => [r.repoId, r]))
|
|
275
|
+
const readmitted = [], changed = []
|
|
276
|
+
for (const repo of target.repos.filter((r) => r.enrolled)) {
|
|
277
|
+
const present = now.get(repo.repoId)
|
|
278
|
+
if (!present) readmitted.push(repo.repoId)
|
|
279
|
+
else if (present.root !== repo.root || present.remote !== repo.remote) changed.push(repo.repoId)
|
|
280
|
+
}
|
|
281
|
+
if (readmitted.length) refuse('rollback-readmits-repository', { repoIds: Object.freeze(readmitted.sort()) })
|
|
282
|
+
if (changed.length) refuse('rollback-identity-changed', { repoIds: Object.freeze(changed.sort()) })
|
|
283
|
+
const added = target.bindings.filter((binding) => !current.bindings.includes(binding))
|
|
284
|
+
if (added.length) refuse('rollback-readds-binding', { count: added.length })
|
|
285
|
+
}
|
|
286
|
+
const summaryOf = (target, current, targets) => ({
|
|
287
|
+
enrollmentRemoved: current.repos.filter((r) => r.enrolled && !target.repos.some((t) => t.enrolled && t.repoId === r.repoId)).map((r) => r.repoId).sort(),
|
|
288
|
+
bindingsRemoved: current.bindings.filter((b) => !target.bindings.includes(b)).length,
|
|
289
|
+
manifestChanged: targets.manifestChanged,
|
|
290
|
+
overlayChanged: targets.overlayChanged,
|
|
291
|
+
})
|
|
292
|
+
export const restoreConfirmDigest = (plan) => `sha256:${hash(canonical({ action: 'restore', generationId: plan.generationId, from: plan.from, to: plan.to }))}`
|
|
293
|
+
|
|
294
|
+
// Plans restoring the authored manifest and overlay recorded by an earlier
|
|
295
|
+
// generation. The target files are validated now. Re-admission or a re-added
|
|
296
|
+
// binding is the person's fresh edit, never a restore's side effect. Restored
|
|
297
|
+
// files are written in the generation's canonical form (sorted keys, no
|
|
298
|
+
// indentation), not the person's original formatting.
|
|
299
|
+
export function planPersonalRestore(options) {
|
|
300
|
+
return safe(() => {
|
|
301
|
+
const { personalHome, generationId } = options ?? {}
|
|
302
|
+
privateHome(personalHome)
|
|
303
|
+
const inputs = recordedInputs(personalHome, generationId)
|
|
304
|
+
validateBytes(personalHome, canonical(inputs.manifest), 'manifest')
|
|
305
|
+
validateBytes(personalHome, canonical(inputs.overlay), 'overlay')
|
|
306
|
+
const manifest = currentAuthored(personalHome, MANIFEST_FILE, 'manifest')
|
|
307
|
+
const overlay = currentAuthored(personalHome, OVERLAY_FILE, 'overlay')
|
|
308
|
+
wideningCheck(inputs.manifest, manifest.value)
|
|
309
|
+
const to = { manifest: hash(canonical(inputs.manifest)), overlay: hash(canonical(inputs.overlay)) }
|
|
310
|
+
const from = { manifest: manifest.digest, overlay: overlay.digest }
|
|
311
|
+
const fields = { generationId, from, to }
|
|
312
|
+
const plan = freeze({ ...fields, summary: summaryOf(inputs.manifest, manifest.value, { manifestChanged: from.manifest !== to.manifest, overlayChanged: from.overlay !== to.overlay }),
|
|
313
|
+
confirm: restoreConfirmDigest(fields) })
|
|
314
|
+
restorePlans.add(plan)
|
|
315
|
+
return plan
|
|
316
|
+
}, 'personal-home-unavailable')
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Replaces `file` with `staged` only if `file` still holds exactly `expected`:
|
|
320
|
+
// the current file is moved aside, its bytes are verified, and the staged file
|
|
321
|
+
// is linked into place, which fails if another writer recreated the name. On any
|
|
322
|
+
// mismatch or failure to read the moved file, it is put back; when its name was
|
|
323
|
+
// taken meanwhile it stays as a listed temporary file. Writers that replace or
|
|
324
|
+
// recreate the file by path therefore keep their bytes. While the swap is in
|
|
325
|
+
// progress the name is briefly absent.
|
|
326
|
+
function compareAndSwap(personalHome, file, staged, expected, key) {
|
|
327
|
+
const aside = path.join(personalHome, `.restore-${randomUUID()}-aside-${key}.json`)
|
|
328
|
+
fs.renameSync(file, aside)
|
|
329
|
+
const putBack = () => {
|
|
330
|
+
try { fs.linkSync(aside, file) } catch { return false }
|
|
331
|
+
try { fs.unlinkSync(aside) } catch {}
|
|
332
|
+
try { syncDir(personalHome) } catch {}
|
|
333
|
+
return true
|
|
334
|
+
}
|
|
335
|
+
let moved
|
|
336
|
+
try { moved = readBytes(aside, MAX_RECORD) } catch { putBack(); refuse('authored-input-changed') }
|
|
337
|
+
if (hash(moved) !== expected) { putBack(); refuse('authored-input-changed') }
|
|
338
|
+
try { fs.linkSync(staged, file) } catch (error) {
|
|
339
|
+
if (error.code === 'EEXIST') refuse('authored-input-changed')
|
|
340
|
+
putBack(); throw error
|
|
341
|
+
}
|
|
342
|
+
fs.unlinkSync(staged)
|
|
343
|
+
fs.unlinkSync(aside)
|
|
344
|
+
syncDir(personalHome)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Restores a plan from planPersonalRestore. The current authored bytes are read
|
|
348
|
+
// once, validated, required to match the plan and re-checked for widening; those
|
|
349
|
+
// exact bytes are kept in an append-only restore record first. Both restored
|
|
350
|
+
// files are staged and validated before either is replaced, then each is
|
|
351
|
+
// replaced by compare-and-swap. A rerun completes an interrupted restore.
|
|
352
|
+
export function restorePersonalInputs(plan, options) {
|
|
353
|
+
return safe(() => {
|
|
354
|
+
const { personalHome, confirm } = options ?? {}
|
|
355
|
+
privateHome(personalHome)
|
|
356
|
+
if (!restorePlans.has(plan) || confirm !== plan.confirm || confirm !== restoreConfirmDigest(plan)) refuse('confirmation-mismatch')
|
|
357
|
+
const inputs = recordedInputs(personalHome, plan.generationId)
|
|
358
|
+
const targets = { manifest: [MANIFEST_FILE, canonical(inputs.manifest)], overlay: [OVERLAY_FILE, canonical(inputs.overlay)] }
|
|
359
|
+
const current = {}
|
|
360
|
+
for (const [key, [name, content]] of Object.entries(targets)) {
|
|
361
|
+
if (hash(content) !== plan.to[key]) refuse('generation-corrupt')
|
|
362
|
+
current[key] = currentAuthored(personalHome, name, key)
|
|
363
|
+
if (current[key].digest !== plan.from[key] && current[key].digest !== plan.to[key]) refuse('authored-input-changed')
|
|
364
|
+
}
|
|
365
|
+
wideningCheck(inputs.manifest, current.manifest.value)
|
|
366
|
+
const dir = privateDir(personalHome, 'restores', true)
|
|
367
|
+
const record = canonical({ schema: RESTORE_SCHEMA, generationId: plan.generationId, from: plan.from, to: plan.to,
|
|
368
|
+
replaced: { manifest: current.manifest.bytes.toString('base64'), overlay: current.overlay.bytes.toString('base64') } })
|
|
369
|
+
publishExclusive(dir, `${Date.now()}-${randomUUID()}.json`, record, 'restore')
|
|
370
|
+
const pending = Object.entries(targets).filter(([key]) => current[key].digest !== plan.to[key])
|
|
371
|
+
const staged = []
|
|
372
|
+
try {
|
|
373
|
+
for (const [key, [, content]] of pending) {
|
|
374
|
+
const file = path.join(personalHome, `.restore-${randomUUID()}-${key}.json`)
|
|
375
|
+
writeExclusive(file, content)
|
|
376
|
+
staged.push([key, file])
|
|
377
|
+
;(key === 'manifest' ? loadPersonalManifest : loadPersonalOverlay)(file, { personalHome })
|
|
378
|
+
}
|
|
379
|
+
for (const [key, file] of staged) compareAndSwap(personalHome, path.join(personalHome, targets[key][0]), file, current[key].digest, key)
|
|
380
|
+
} finally {
|
|
381
|
+
for (const [, file] of staged) fs.rmSync(file, { force: true })
|
|
382
|
+
}
|
|
383
|
+
return freeze({ generationId: plan.generationId, ...eligibility(personalHome, plan.generationId) })
|
|
384
|
+
}, 'restore-write-failed')
|
|
385
|
+
}
|
package/src/project/config.mjs
CHANGED
|
@@ -556,6 +556,14 @@ export function validateProjectConfigDoc(doc, { neutralTemplate = false } = {})
|
|
|
556
556
|
if (setup.profile != null && !['single-repo', 'private-domain', 'shared-project', 'multi-repo', 'monorepo', 'control-workspace'].includes(setup.profile)) {
|
|
557
557
|
errors.push('setup.profile is invalid')
|
|
558
558
|
}
|
|
559
|
+
// The census reads these as path patterns relative to the project config's
|
|
560
|
+
// folder (docs/knowledge-graph.md), under the contract's pathString rules.
|
|
561
|
+
for (const key of ['include', 'exclude']) {
|
|
562
|
+
errors.push(...stringPathErrors(setup[key], `setup.${key}`))
|
|
563
|
+
if (typeof setup[key] === 'string' && (setup[key].includes('\u0000') || /^[a-z][a-z0-9+.-]*:/.test(setup[key]) || path.isAbsolute(setup[key]))) {
|
|
564
|
+
errors.push(`setup.${key} must be a path pattern relative to the project config folder`)
|
|
565
|
+
}
|
|
566
|
+
}
|
|
559
567
|
if (alignment.appRepo != null && !firstString(alignment.appRepo)) errors.push('alignment.appRepo must be a non-empty string')
|
|
560
568
|
|
|
561
569
|
const repoNames = new Set()
|
|
@@ -149,7 +149,9 @@ export function createVerifiedFileSequence({ directory, initial, apply, ignoreFi
|
|
|
149
149
|
if (next.length < names.length) throw new Error('history shortened; preserve and inspect');
|
|
150
150
|
const ids = next.map(n => identity(path.join(directory, n)));
|
|
151
151
|
const reuse = names.every((n, i) => n === next[i] && identities[i] === ids[i]);
|
|
152
|
-
|
|
152
|
+
// Replay into a copy: a refused read must not leave the verified value
|
|
153
|
+
// half-applied, or every later read of this reader would be refused.
|
|
154
|
+
let result = reuse ? structuredClone(value) : initial();
|
|
153
155
|
for (let i = reuse ? names.length : 0; i < next.length; i++) {
|
|
154
156
|
const file = path.join(directory, next[i]);
|
|
155
157
|
result = apply(readRegularTextNoFollow(file), result, i + 1, next[i]);
|
|
@@ -188,7 +188,7 @@ export function classifyManagedPath(relativePath) {
|
|
|
188
188
|
// moves up a level. Any other failure (a directory that cannot be searched,
|
|
189
189
|
// a link loop) is thrown: a lexical path standing in for a real one would
|
|
190
190
|
// weaken the alias check exactly where it matters.
|
|
191
|
-
function realLocation(target, realpath) {
|
|
191
|
+
export function realLocation(target, realpath = fs.realpathSync.native) {
|
|
192
192
|
const missing = []
|
|
193
193
|
let current = path.resolve(target)
|
|
194
194
|
for (;;) {
|
|
@@ -62,7 +62,7 @@ export function openRegularFileNoFollow(file, flags = fs.constants.O_RDONLY, mod
|
|
|
62
62
|
// leaf before open and bind the opened descriptor back to that same file
|
|
63
63
|
// identity where the filesystem supplies stable device/inode values.
|
|
64
64
|
if (before && before.ino !== 0 && (before.dev !== opened.dev || before.ino !== opened.ino)) {
|
|
65
|
-
throw new Error('state leaf changed while opening')
|
|
65
|
+
throw Object.assign(new Error('state leaf changed while opening'), { code: 'ELEAFCHANGED' })
|
|
66
66
|
}
|
|
67
67
|
return descriptor
|
|
68
68
|
} catch (error) {
|
|
@@ -80,6 +80,15 @@ export function readRegularTextNoFollow(file) {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// The real path of a target as the file system stores it: on a volume that
|
|
84
|
+
// folds letter case (macOS by default) each component in the case it is stored
|
|
85
|
+
// in, which is also how the operating system reports a working directory, while
|
|
86
|
+
// Node's own resolution keeps the case it was given. On Windows Node's own
|
|
87
|
+
// resolution is kept: the native one also expands short 8.3 names.
|
|
88
|
+
export function realPathAsStored(target) {
|
|
89
|
+
return process.platform === 'win32' ? fs.realpathSync(target) : fs.realpathSync.native(target)
|
|
90
|
+
}
|
|
91
|
+
|
|
83
92
|
export function syncPrivateDirectory(directory) {
|
|
84
93
|
let fd;
|
|
85
94
|
try { fd = fs.openSync(directory, fs.constants.O_RDONLY); fs.fsyncSync(fd); }
|