@tiinex/app 0.1.1

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.
Files changed (580) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +31 -0
  3. package/README.md +26 -0
  4. package/docs/CONSUMER-CONTRACT.md +65 -0
  5. package/package.json +94 -0
  6. package/src/acceptance/knownScenarios.case.mjs +48 -0
  7. package/src/acceptance/knownScenarios.js +89 -0
  8. package/src/acceptance/postV423CanonicalTransitionProductVerticalSlice.case.mjs +774 -0
  9. package/src/acceptance/postV437M0ETimePortalProductParityClosure.case.mjs +51 -0
  10. package/src/acceptance/postV447M0FGithubSocialPublicationProductIntegration.case.mjs +33 -0
  11. package/src/acceptance/postV450M0ProductAcceptanceHardening.case.mjs +82 -0
  12. package/src/acceptance/postV470SchemaReadingContractMaterializationIdentitySourceCoalescingAuthorityCorrection.case.mjs +236 -0
  13. package/src/acceptance/recoveryAcceptance.case.mjs +196 -0
  14. package/src/adapters/adapter.definition.schema.json +33 -0
  15. package/src/adapters/archive/archive.adapter.case.mjs +212 -0
  16. package/src/adapters/export/export.adapter.js +79 -0
  17. package/src/adapters/git-native/git-native.adapter.js +43 -0
  18. package/src/adapters/github/github.adapter.case.mjs +843 -0
  19. package/src/adapters/github/github.adapter.js +395 -0
  20. package/src/adapters/github/github.issueEmbedded.js +262 -0
  21. package/src/adapters/github/github.issueMirror.js +309 -0
  22. package/src/adapters/github/github.issueSnapshot.js +475 -0
  23. package/src/adapters/github/github.issueSurface.js +188 -0
  24. package/src/adapters/github/github.issueTransport.js +237 -0
  25. package/src/adapters/github/github.publicationRead.js +24 -0
  26. package/src/adapters/github/github.readTransport.js +18 -0
  27. package/src/adapters/github/github.repoDiscovery.js +222 -0
  28. package/src/adapters/github/github.repoMirror.js +255 -0
  29. package/src/adapters/github/github.repoProxy.js +276 -0
  30. package/src/adapters/github/github.repoSurface.js +60 -0
  31. package/src/adapters/github/github.transportDiagnostics.js +68 -0
  32. package/src/adapters/local/local.adapter.case.mjs +72 -0
  33. package/src/adapters/local/local.adapter.js +188 -0
  34. package/src/adapters/registry.js +35 -0
  35. package/src/adapters/static/static.adapter.js +84 -0
  36. package/src/app/App.js +1 -0
  37. package/src/app/TiinexApp.jsx +822 -0
  38. package/src/app/appShell.views.jsx +58 -0
  39. package/src/app/artifactAuthoringCapability.js +49 -0
  40. package/src/app/boot/bootApp.js +19 -0
  41. package/src/app/boot/environment.js +1 -0
  42. package/src/app/canonicalAuthoringValues.js +4 -0
  43. package/src/app/canonicalCreationProductController.js +57 -0
  44. package/src/app/canonicalCreationProductSettlement.case.mjs +10 -0
  45. package/src/app/canonicalCreationProductSettlement.js +11 -0
  46. package/src/app/canonicalReferenceLocalCreateCommand.js +210 -0
  47. package/src/app/canonicalReferenceTargets.js +73 -0
  48. package/src/app/canonicalTransitionLocalCreateCommand.js +217 -0
  49. package/src/app/defaultWorkspaceStart.case.mjs +39 -0
  50. package/src/app/defaultWorkspaceStart.js +65 -0
  51. package/src/app/defaultWorkspaceStartCommand.js +55 -0
  52. package/src/app/durableLocalMutationPolicy.js +21 -0
  53. package/src/app/githubMaterializationSummary.js +69 -0
  54. package/src/app/githubProgress.js +25 -0
  55. package/src/app/githubSourceInput.js +47 -0
  56. package/src/app/githubSourceMaterializationCommand.js +97 -0
  57. package/src/app/githubSourceOperation.js +226 -0
  58. package/src/app/governanceDialogData.js +27 -0
  59. package/src/app/handoffPackageImportCommand.js +182 -0
  60. package/src/app/historyAuthority.js +53 -0
  61. package/src/app/initialWorkspaceBootstrapOperation.js +105 -0
  62. package/src/app/lineageCommand.js +118 -0
  63. package/src/app/lineageSourceRecovery.js +339 -0
  64. package/src/app/localDraftMutationCommand.js +174 -0
  65. package/src/app/localMaterialCommand.js +124 -0
  66. package/src/app/nodeGraphVerse.case.mjs +111 -0
  67. package/src/app/persistenceOwnership.js +92 -0
  68. package/src/app/productStateBoundary.js +7 -0
  69. package/src/app/providers/AppProviders.js +1 -0
  70. package/src/app/providers/LocaleProvider.js +1 -0
  71. package/src/app/providers/SchemaRegistryProvider.js +1 -0
  72. package/src/app/providers/WorkspaceProvider.js +1 -0
  73. package/src/app/publicTarget.js +251 -0
  74. package/src/app/publicTargetRestoreCommand.js +149 -0
  75. package/src/app/recordUi.cache.js +35 -0
  76. package/src/app/recordUi.js +11 -0
  77. package/src/app/routes/RouteSurface.js +1 -0
  78. package/src/app/routes/routes.js +1 -0
  79. package/src/app/runtimeState.js +54 -0
  80. package/src/app/schemaCurrentDeclaration.js +69 -0
  81. package/src/app/schemaFactoryLocalCreateCommand.js +101 -0
  82. package/src/app/schemaFactoryViewerProof.case.mjs +134 -0
  83. package/src/app/schemaFactoryViewerProof.js +20 -0
  84. package/src/app/schemaNavigationCommand.js +285 -0
  85. package/src/app/schemaNavigationMaterialization.js +171 -0
  86. package/src/app/schemaNavigationRuntimeCatalog.js +25 -0
  87. package/src/app/schemaNavigationState.js +66 -0
  88. package/src/app/schemaReadingContractQualification.js +91 -0
  89. package/src/app/schemaSourceRecovery.js +373 -0
  90. package/src/app/scrollPersistence.js +30 -0
  91. package/src/app/semanticOperationHistory.js +18 -0
  92. package/src/app/shareActionCommand.js +55 -0
  93. package/src/app/shareProjection.js +190 -0
  94. package/src/app/shell/AppShell.js +1 -0
  95. package/src/app/shell/Header.js +1 -0
  96. package/src/app/shell/Navigation.js +1 -0
  97. package/src/app/shell/SurfaceSwitcher.js +1 -0
  98. package/src/app/sourceTransportRefresh.js +176 -0
  99. package/src/app/startupOwnership.js +40 -0
  100. package/src/app/startupPresentation.case.mjs +34 -0
  101. package/src/app/startupPresentation.js +33 -0
  102. package/src/app/startupPresentation.views.jsx +21 -0
  103. package/src/app/startupRenderPhase.js +9 -0
  104. package/src/app/startupWorkspaceCommand.js +62 -0
  105. package/src/app/startupWorkspaceConfigApplication.js +49 -0
  106. package/src/app/statePersistenceScheduler.js +106 -0
  107. package/src/app/tiinexAppConfigPlan.js +108 -0
  108. package/src/app/tiinexAppConfigSource.js +349 -0
  109. package/src/app/tiinexAppStartupSource.js +79 -0
  110. package/src/app/tiinexHostedWorkspaceConventions.js +572 -0
  111. package/src/app/timePortalHistoricalRead.js +114 -0
  112. package/src/app/timePortalSnapshotResolution.js +53 -0
  113. package/src/app/urlMaterialCommand.js +24 -0
  114. package/src/app/useCanonicalPlacementSelectionOptions.js +9 -0
  115. package/src/app/useCanonicalReferenceSelectionOptions.js +8 -0
  116. package/src/app/useLocalMaterialIntake.js +68 -0
  117. package/src/app/useTimePortalProductController.jsx +111 -0
  118. package/src/app/useWorkspaceEntrypointIntake.js +77 -0
  119. package/src/app/useWorkspaceSelectionProductController.jsx +21 -0
  120. package/src/app/viewState.js +35 -0
  121. package/src/app/viewport.js +21 -0
  122. package/src/app/visualDormancy.js +286 -0
  123. package/src/app/workspaceContinuityNotices.js +32 -0
  124. package/src/app/workspaceDisplayCounts.js +9 -0
  125. package/src/app/workspaceEntrypointIntakeCommand.js +87 -0
  126. package/src/app/workspaceEntrypointProductRouting.case.mjs +20 -0
  127. package/src/app/workspaceExportCommand.js +21 -0
  128. package/src/app/workspaceExportDownload.js +23 -0
  129. package/src/app/workspaceExportExecutionCommand.js +10 -0
  130. package/src/app/workspaceGithubPublication.js +237 -0
  131. package/src/app/workspaceHandoffExport.js +56 -0
  132. package/src/app/workspaceHomeTarget.js +9 -0
  133. package/src/app/workspaceMulticolumn.js +113 -0
  134. package/src/app/workspaceNavigationParity.case.mjs +110 -0
  135. package/src/app/workspaceOpenProgress.js +26 -0
  136. package/src/app/workspacePlacementOptions.js +46 -0
  137. package/src/app/workspaceRecordActions.js +158 -0
  138. package/src/app/workspaceScopedInteraction.js +68 -0
  139. package/src/app/workspaceSelectionOriginContext.js +29 -0
  140. package/src/app/workspaceSelectionSession.js +54 -0
  141. package/src/app/workspaceSourceCloseCommand.js +25 -0
  142. package/src/app/workspaceSpineProductRouting.case.mjs +22 -0
  143. package/src/app/workspaceStartupTransition.js +32 -0
  144. package/src/app/workspaceWindow.js +85 -0
  145. package/src/artifacts/artifact.fixture.js +1 -0
  146. package/src/artifacts/artifact.identity.js +6 -0
  147. package/src/artifacts/artifact.identityAggregation.js +218 -0
  148. package/src/artifacts/artifact.model.js +2 -0
  149. package/src/artifacts/artifact.parse.case.mjs +196 -0
  150. package/src/artifacts/artifact.participantIndex.js +189 -0
  151. package/src/artifacts/fixtures/evidence.trace.md +50 -0
  152. package/src/artifacts/fixtures/topic.trace.md +33 -0
  153. package/src/artifacts/fixtures/unknown-schema.trace.md +25 -0
  154. package/src/audit/audit.findings.js +1 -0
  155. package/src/audit/audit.model.js +2 -0
  156. package/src/audit/audit.plan.js +13 -0
  157. package/src/audit/audit.report.js +12 -0
  158. package/src/audit/audit.severity.js +1 -0
  159. package/src/audit/audit.traverse.js +119 -0
  160. package/src/audit/integrity/auditIntegrity.checks.js +1 -0
  161. package/src/audit/integrity/auditIntegrity.compare.js +1 -0
  162. package/src/audit/lineage/auditLineage.load.js +1 -0
  163. package/src/audit/lineage/auditLineage.plan.js +1 -0
  164. package/src/audit/lineage/auditLineage.resolve.js +12 -0
  165. package/src/audit/sources/auditSources.boundaries.js +1 -0
  166. package/src/audit/sources/auditSources.checks.js +1 -0
  167. package/src/audit/validation/auditValidation.recheck.js +1 -0
  168. package/src/audit/validation/auditValidation.run.js +1 -0
  169. package/src/build.identity.js +15 -0
  170. package/src/commands/command.vocabulary.js +14 -0
  171. package/src/conformance/conformance.fixtures.js +151 -0
  172. package/src/conformance/conformance.run.js +238 -0
  173. package/src/data/applicationDataStore.js +23 -0
  174. package/src/data/companionAccess.js +35 -0
  175. package/src/diagnostics/events.js +1 -0
  176. package/src/diagnostics/invariant.js +1 -0
  177. package/src/diagnostics/schemaBindingChecks.js +1 -0
  178. package/src/diagnostics/schemaModuleChecks.js +1 -0
  179. package/src/diagnostics/sourceBoundaryChecks.js +5 -0
  180. package/src/diagnostics/sourceTransport.report.js +190 -0
  181. package/src/discovery/discovery.controls.js +7 -0
  182. package/src/export/export.plan.js +182 -0
  183. package/src/export/handoff.plan.js +50 -0
  184. package/src/export/package.builder.case.mjs +97 -0
  185. package/src/export/package.controlTopology.case.mjs +130 -0
  186. package/src/export/package.zip.case.mjs +33 -0
  187. package/src/graph/artifactGraph.model.js +344 -0
  188. package/src/graph/artifactGraph.project.js +139 -0
  189. package/src/graph/artifactGraph.worker.js +10 -0
  190. package/src/i18n/i18n.keys.js +1 -0
  191. package/src/i18n/i18n.provider.js +1 -0
  192. package/src/i18n/i18n.resolve.js +5 -0
  193. package/src/i18n/locales/en/common.json +4 -0
  194. package/src/i18n/locales/en/schemas/evidence.json +3 -0
  195. package/src/i18n/locales/en/schemas/presentation-surface.json +3 -0
  196. package/src/i18n/locales/en/schemas/preservation.json +3 -0
  197. package/src/i18n/locales/en/schemas/root.json +3 -0
  198. package/src/i18n/locales/en/schemas/schema-module.json +3 -0
  199. package/src/i18n/locales/en/schemas/topic.json +3 -0
  200. package/src/i18n/locales/en/surfaces/audit.json +3 -0
  201. package/src/i18n/locales/en/surfaces/create.json +3 -0
  202. package/src/i18n/locales/en/surfaces/detail.json +3 -0
  203. package/src/i18n/locales/en/surfaces/display-options.json +3 -0
  204. package/src/i18n/locales/en/surfaces/edit.json +3 -0
  205. package/src/i18n/locales/en/surfaces/feed.json +3 -0
  206. package/src/i18n/locales/en/surfaces/lineage.json +3 -0
  207. package/src/i18n/locales/en/surfaces/preview.json +3 -0
  208. package/src/i18n/locales/en/surfaces/share.json +3 -0
  209. package/src/i18n/locales/en/surfaces/source-settings.json +3 -0
  210. package/src/i18n/locales/en/surfaces/tree.json +3 -0
  211. package/src/i18n/locales/sv/common.json +4 -0
  212. package/src/i18n/locales/sv/schemas/evidence.json +3 -0
  213. package/src/i18n/locales/sv/schemas/presentation-surface.json +3 -0
  214. package/src/i18n/locales/sv/schemas/preservation.json +3 -0
  215. package/src/i18n/locales/sv/schemas/root.json +3 -0
  216. package/src/i18n/locales/sv/schemas/schema-module.json +3 -0
  217. package/src/i18n/locales/sv/schemas/topic.json +3 -0
  218. package/src/i18n/locales/sv/surfaces/audit.json +3 -0
  219. package/src/i18n/locales/sv/surfaces/create.json +3 -0
  220. package/src/i18n/locales/sv/surfaces/detail.json +3 -0
  221. package/src/i18n/locales/sv/surfaces/display-options.json +3 -0
  222. package/src/i18n/locales/sv/surfaces/edit.json +3 -0
  223. package/src/i18n/locales/sv/surfaces/feed.json +3 -0
  224. package/src/i18n/locales/sv/surfaces/lineage.json +3 -0
  225. package/src/i18n/locales/sv/surfaces/preview.json +3 -0
  226. package/src/i18n/locales/sv/surfaces/share.json +3 -0
  227. package/src/i18n/locales/sv/surfaces/source-settings.json +3 -0
  228. package/src/i18n/locales/sv/surfaces/tree.json +3 -0
  229. package/src/index.js +10 -0
  230. package/src/integrity/integrity.testFixture.js +9 -0
  231. package/src/interaction/interaction.spine.js +12 -0
  232. package/src/lineage/lineage.audit.js +1 -0
  233. package/src/lineage/lineage.edges.js +1 -0
  234. package/src/main.js +319 -0
  235. package/src/main.jsx +34 -0
  236. package/src/multiverse/multiverse.model.js +10 -0
  237. package/src/parity/poc.m1ParityScenarios.js +42 -0
  238. package/src/parity/poc.parityLedger.js +261 -0
  239. package/src/publication/publication.githubSocialContract.case.mjs +247 -0
  240. package/src/publication/publication.preflight.case.mjs +64 -0
  241. package/src/react/TiinexApplication.jsx +39 -0
  242. package/src/react/VerseHost.jsx +29 -0
  243. package/src/react/applicationDataContext.jsx +30 -0
  244. package/src/react.js +2 -0
  245. package/src/readers/modes/audit.reader.js +1 -0
  246. package/src/readers/modes/detail.reader.js +1 -0
  247. package/src/readers/modes/handover.reader.js +1 -0
  248. package/src/readers/modes/mobile.reader.js +1 -0
  249. package/src/readers/modes/print.reader.js +1 -0
  250. package/src/readers/modes/scan.reader.js +1 -0
  251. package/src/readers/reader.model.js +1 -0
  252. package/src/readers/reader.resolve.js +2 -0
  253. package/src/reingest/reingest.plan.case.mjs +65 -0
  254. package/src/runtime.js +54 -0
  255. package/src/schemas/README.md +99 -0
  256. package/src/schemas/coordination/handoff/tiinex.handoff.v1.schema.md +614 -0
  257. package/src/schemas/coordination/project/tiinex.project.v1.schema.md +311 -0
  258. package/src/schemas/core/decision/tiinex.decision.v1.schema.md +227 -0
  259. package/src/schemas/core/evidence/tiinex.evidence.v1.schema.md +318 -0
  260. package/src/schemas/core/feedback/tiinex.feedback.v1.schema.md +243 -0
  261. package/src/schemas/core/interpretation/.transitions/evidence-to-interpretation-transition-definition.trace.md +102 -0
  262. package/src/schemas/core/interpretation/tiinex.interpretation.v1.schema.md +308 -0
  263. package/src/schemas/core/preservation/tiinex.preservation.v1.schema.md +312 -0
  264. package/src/schemas/core/relation/.generation/reference-relation-generation-authority.trace.md +185 -0
  265. package/src/schemas/core/relation/.transitions/topic-references-task-transition-definition.trace.md +119 -0
  266. package/src/schemas/core/relation/tiinex.relation.v1.schema.md +240 -0
  267. package/src/schemas/core/relation/workspace/representation/tiinex.workspace.representation.v1.schema.md +550 -0
  268. package/src/schemas/core/signal/tiinex.signal.v1.schema.md +239 -0
  269. package/src/schemas/core/task/.transitions/create-task-transition-definition.trace.md +93 -0
  270. package/src/schemas/core/task/.transitions/topic-to-task-transition-definition.trace.md +102 -0
  271. package/src/schemas/core/task/task-semantic-package.trace.md +53 -0
  272. package/src/schemas/core/task/tiinex.task.v1-transitions.trace.md +33 -0
  273. package/src/schemas/core/task/tiinex.task.v1.authoring.js +65 -0
  274. package/src/schemas/core/task/tiinex.task.v1.schema.md +229 -0
  275. package/src/schemas/core/topic/.transitions/create-topic-transition-definition.trace.md +93 -0
  276. package/src/schemas/core/topic/tiinex.topic.v1-transitions.trace.md +33 -0
  277. package/src/schemas/core/topic/tiinex.topic.v1.schema.md +197 -0
  278. package/src/schemas/core/topic/topic-semantic-package.trace.md +53 -0
  279. package/src/schemas/discovery/finding/tiinex.discovery.finding.v1.schema.md +240 -0
  280. package/src/schemas/discovery/tiinex.discovery.v1.schema.md +294 -0
  281. package/src/schemas/manifest.json +367 -0
  282. package/src/schemas/origins.js +46 -0
  283. package/src/schemas/party/organization/tiinex.party.organization.v1.schema.md +290 -0
  284. package/src/schemas/party/role/tiinex.party.role.v1.schema.md +298 -0
  285. package/src/schemas/party/tiinex.party.v1.schema.js +14 -0
  286. package/src/schemas/party/tiinex.party.v1.schema.json +23 -0
  287. package/src/schemas/party/tiinex.party.v1.schema.md +311 -0
  288. package/src/schemas/party/tiinex.party.v1.schema.runtime.json +2033 -0
  289. package/src/schemas/party/tiinex.party.v1.schema.source.js +9 -0
  290. package/src/schemas/presentation/surface/tiinex.presentation.surface.v1.schema.md +476 -0
  291. package/src/schemas/reduction/tiinex.reduction.v1.schema.md +248 -0
  292. package/src/schemas/schema/module/tiinex.schema.module.v1.schema.md +465 -0
  293. package/src/schemas/schema.companionContract.case.mjs +259 -0
  294. package/src/schemas/schema.factory.case.mjs +256 -0
  295. package/src/schemas/schema.factory.viewerProjection.js +183 -0
  296. package/src/schemas/schemaMarkdownCatalog.js +19 -0
  297. package/src/schemas/tiinex.root.v1.schema.md +865 -0
  298. package/src/schemas/validation/finding/tiinex.validation.finding.v1.schema.md +309 -0
  299. package/src/schemas/validation/method/tiinex.validation.method.v1.schema.md +280 -0
  300. package/src/schemas/validation/report/tiinex.validation.report.v1.schema.md +300 -0
  301. package/src/schemas/workspace/tiinex.workspace.v1.schema.md +486 -0
  302. package/src/schemas/workspace/workspace.add.views.jsx +327 -0
  303. package/src/schemas/workspace/workspace.audit.views.jsx +160 -0
  304. package/src/schemas/workspace/workspace.auditBadge.views.jsx +98 -0
  305. package/src/schemas/workspace/workspace.canonicalReferenceDialog.views.jsx +63 -0
  306. package/src/schemas/workspace/workspace.canonicalTaskDialog.views.jsx +169 -0
  307. package/src/schemas/workspace/workspace.cardActions.js +9 -0
  308. package/src/schemas/workspace/workspace.cards.views.jsx +146 -0
  309. package/src/schemas/workspace/workspace.chrome.views.jsx +299 -0
  310. package/src/schemas/workspace/workspace.continuationDialog.views.jsx +80 -0
  311. package/src/schemas/workspace/workspace.discovery.views.jsx +63 -0
  312. package/src/schemas/workspace/workspace.discoveryRenderWindow.js +56 -0
  313. package/src/schemas/workspace/workspace.displayOptions.views.jsx +114 -0
  314. package/src/schemas/workspace/workspace.entrypointChoice.views.jsx +30 -0
  315. package/src/schemas/workspace/workspace.exportDialog.controller.jsx +40 -0
  316. package/src/schemas/workspace/workspace.exportDialog.views.jsx +142 -0
  317. package/src/schemas/workspace/workspace.governance.views.jsx +45 -0
  318. package/src/schemas/workspace/workspace.graph.views.jsx +134 -0
  319. package/src/schemas/workspace/workspace.lineage.views.jsx +307 -0
  320. package/src/schemas/workspace/workspace.read.views.jsx +105 -0
  321. package/src/schemas/workspace/workspace.recordActionButton.views.jsx +35 -0
  322. package/src/schemas/workspace/workspace.recordDialogs.views.jsx +257 -0
  323. package/src/schemas/workspace/workspace.selection.views.jsx +14 -0
  324. package/src/schemas/workspace/workspace.sourceReceipt.views.jsx +40 -0
  325. package/src/schemas/workspace/workspace.timePortal.views.jsx +56 -0
  326. package/src/schemas/workspace/workspace.tree.views.jsx +107 -0
  327. package/src/schemas/workspace/workspace.viewFormatting.js +172 -0
  328. package/src/schemas/workspace/workspace.views.jsx +210 -0
  329. package/src/source-settings/sourceSettings.model.js +7 -0
  330. package/src/sources/draft/draft.source.js +1 -0
  331. package/src/sources/github/github.loader.js +204 -0
  332. package/src/sources/github/github.permalink.js +1 -0
  333. package/src/sources/github/github.source.js +1 -0
  334. package/src/sources/github/github.transport.case.mjs +237 -0
  335. package/src/sources/github/github.transport.js +472 -0
  336. package/src/sources/local/local.source.js +1 -0
  337. package/src/sources/source.assetReferences.js +109 -0
  338. package/src/sources/source.boundaries.js +24 -0
  339. package/src/sources/source.identity.js +56 -0
  340. package/src/sources/source.model.case.mjs +26 -0
  341. package/src/sources/source.model.js +56 -0
  342. package/src/sources/source.presenter.js +66 -0
  343. package/src/sources/source.resolve.js +11 -0
  344. package/src/sources/static/static.source.js +1 -0
  345. package/src/sources/transport.policy.case.mjs +39 -0
  346. package/src/sources/transport.policy.js +81 -0
  347. package/src/storage/storage.policy.js +79 -0
  348. package/src/styles/app.css +8699 -0
  349. package/src/styles/responsive.css +10 -0
  350. package/src/styles/theme.css +4 -0
  351. package/src/styles/tokens.css +30 -0
  352. package/src/surfaces/audit/AuditSurface.js +1 -0
  353. package/src/surfaces/audit/audit.model.js +1 -0
  354. package/src/surfaces/audit/audit.render.js +1 -0
  355. package/src/surfaces/contracts.js +45 -0
  356. package/src/surfaces/create/CreateSurface.js +1 -0
  357. package/src/surfaces/create/create.model.js +1 -0
  358. package/src/surfaces/create/create.render.js +1 -0
  359. package/src/surfaces/detail/DetailSurface.js +1 -0
  360. package/src/surfaces/detail/detail.model.js +1 -0
  361. package/src/surfaces/detail/detail.render.js +1 -0
  362. package/src/surfaces/display-options/DisplayOptionsSurface.js +1 -0
  363. package/src/surfaces/display-options/displayOptions.model.js +1 -0
  364. package/src/surfaces/display-options/displayOptions.render.js +1 -0
  365. package/src/surfaces/edit/EditSurface.js +1 -0
  366. package/src/surfaces/edit/edit.model.js +1 -0
  367. package/src/surfaces/edit/edit.render.js +1 -0
  368. package/src/surfaces/feed/FeedSurface.js +1 -0
  369. package/src/surfaces/feed/feed.model.js +1 -0
  370. package/src/surfaces/feed/feed.render.js +1 -0
  371. package/src/surfaces/lineage/LineageSurface.js +1 -0
  372. package/src/surfaces/lineage/lineage.model.js +1 -0
  373. package/src/surfaces/lineage/lineage.render.js +1 -0
  374. package/src/surfaces/preview/PreviewSurface.js +1 -0
  375. package/src/surfaces/preview/preview.model.js +1 -0
  376. package/src/surfaces/preview/preview.render.js +1 -0
  377. package/src/surfaces/registry.js +58 -0
  378. package/src/surfaces/share/ShareSurface.js +1 -0
  379. package/src/surfaces/share/share.model.js +1 -0
  380. package/src/surfaces/share/share.render.js +1 -0
  381. package/src/surfaces/source-settings/SourceSettingsSurface.js +1 -0
  382. package/src/surfaces/source-settings/sourceSettings.model.js +1 -0
  383. package/src/surfaces/source-settings/sourceSettings.render.js +1 -0
  384. package/src/surfaces/surfaceContext.js +1 -0
  385. package/src/surfaces/tree/TreeSurface.js +1 -0
  386. package/src/surfaces/tree/tree.model.js +1 -0
  387. package/src/surfaces/tree/tree.render.js +1 -0
  388. package/src/tooling/portable/adapters/cli/cli.common-output.case.mjs +153 -0
  389. package/src/tooling/portable/adapters/cli/cli.handoff-sibling-allocation.case.mjs +52 -0
  390. package/src/tooling/portable/adapters/cli/cli.run.case.mjs +372 -0
  391. package/src/tooling/portable/adapters/cli/cli.summaryProjection.case.mjs +46 -0
  392. package/src/tooling/portable/audit/audit.capability.case.mjs +45 -0
  393. package/src/tooling/portable/fixtures/legacy-artifacts/collaboration/dogfood/001-tiinex-development-dogfood-collaboration-model.trace.fixture.txt +37 -0
  394. package/src/tooling/portable/fixtures/legacy-artifacts/handoff/tooling/001-v480-tooling-workflow-schema-enablement-handoff.trace.fixture.txt +108 -0
  395. package/src/tooling/portable/fixtures/legacy-artifacts/handoff/tooling/002-v481-tooling-recipient-relative-handoff-material-closure-planner-foundation-handoff.trace.fixture.txt +130 -0
  396. package/src/tooling/portable/fixtures/legacy-artifacts/loom/role/001-loom-role.trace.fixture.txt +61 -0
  397. package/src/tooling/portable/fixtures/legacy-artifacts/sigma/role/001-1-sigma-role.trace.fixture.txt +61 -0
  398. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/001-1-1-1-1-1-1-1-site-tooling-v474-loaded-parent-identity-evidence-closure-result.trace.fixture.txt +48 -0
  399. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/001-1-1-1-1-1-1-site-tooling-v474-loaded-parent-identity-evidence-closure.trace.fixture.txt +48 -0
  400. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/001-1-1-1-1-1-site-tooling-v473-portable-parent-authority-coherence-metadata-fidelity-closure-result.trace.fixture.txt +48 -0
  401. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/001-1-1-1-1-site-tooling-v473-portable-parent-authority-coherence-metadata-fidelity-closure.trace.fixture.txt +48 -0
  402. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/001-1-1-1-site-tooling-v472-portable-exact-authoring-fidelity-closure-result.trace.fixture.txt +48 -0
  403. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/001-1-1-site-tooling-v472-portable-exact-authoring-fidelity-closure.trace.fixture.txt +48 -0
  404. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/001-1-site-tooling-v471-portable-lineage-authoring-closure-result.trace.fixture.txt +48 -0
  405. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/001-site-tooling-v471-portable-lineage-authoring-closure.trace.fixture.txt +39 -0
  406. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/002-1-v475-canonical-artifact-envelope-reference-integrity-validation.trace.fixture.txt +43 -0
  407. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/003-1-v476-canonical-authority-binding-integrity-method-lineage-alloca.trace.fixture.txt +42 -0
  408. package/src/tooling/portable/fixtures/legacy-artifacts/tooling/dogfood/027-handoff-package-workspace-archive-and-control-plane-minimality-audit.trace.fixture.txt +56 -0
  409. package/src/tooling/portable/fixtures/legacyArtifactFixtures.mjs +34 -0
  410. package/src/tooling/portable/grounding/grounding.capsule.case.mjs +65 -0
  411. package/src/tooling/portable/grounding/grounding.readiness.case.mjs +543 -0
  412. package/src/tooling/portable/grounding/grounding.semanticIntegration.case.mjs +131 -0
  413. package/src/tooling/portable/handoff/boundedWorkspaceRepresentation.case.mjs +103 -0
  414. package/src/tooling/portable/handoff/carrierLineage.fixedWidth.case.mjs +97 -0
  415. package/src/tooling/portable/handoff/coldStartQualification.case.mjs +293 -0
  416. package/src/tooling/portable/handoff/fixtures/027-invalid-audit-result.fixture.txt +192 -0
  417. package/src/tooling/portable/handoff/fixtures/027-invalid-return-handoff.fixture.txt +92 -0
  418. package/src/tooling/portable/handoff/fixtures/cold-start-qualification.v1.examples.json +60 -0
  419. package/src/tooling/portable/handoff/handoff.manufacture.case.mjs +293 -0
  420. package/src/tooling/portable/handoff/materialClosure.case.mjs +727 -0
  421. package/src/tooling/portable/handoff/multiRootManufacture.case.mjs +615 -0
  422. package/src/tooling/portable/handoff/operatorBridgeCore.case.mjs +205 -0
  423. package/src/tooling/portable/handoff/workspaceLandingPlan.case.mjs +143 -0
  424. package/src/tooling/portable/input/node.input.case.mjs +293 -0
  425. package/src/tooling/portable/lifecycle/lifecycle.readiness.case.mjs +179 -0
  426. package/src/tooling/portable/lineage/fixtures/lineage.integrity.repair-projection.v1.examples.json +605 -0
  427. package/src/tooling/portable/lineage/lineage.integrity.shared-capability.case.mjs +37 -0
  428. package/src/tooling/portable/lineage/publicationProviderReceipt.fixture.mjs +50 -0
  429. package/src/tooling/portable/package/fixtures/reference-relation-generation-authority.trace.md +84 -0
  430. package/src/tooling/portable/package/fixtures/tiinex.root.v1.package-authority.contract-fixture.md +64 -0
  431. package/src/tooling/portable/package/fixtures/tiinex.schema.generation.v1.generation-binding.contract-fixture.md +140 -0
  432. package/src/tooling/portable/package/fixtures/tiinex.schema.transition.companion.v1.contract-fixture.md +73 -0
  433. package/src/tooling/portable/package/fixtures/tiinex.semantic.package.v1.contract-fixture.md +147 -0
  434. package/src/tooling/portable/package/fixtures/tiinex.transition.definition.v1.package-contract-fixture.md +77 -0
  435. package/src/tooling/portable/providers/schema.providers.genericity.case.mjs +31 -0
  436. package/src/tooling/portable/reduction/reduction.preflight.case.mjs +298 -0
  437. package/src/tooling/portable/schema/fixtures/evidence-preservation-body.inheritance-record-fixture.md +65 -0
  438. package/src/tooling/portable/schema/fixtures/tiinex.portal.time.v1.contract-fixture.md +294 -0
  439. package/src/tooling/portable/schema/fixtures/tiinex.root.v1.contract-fixture.md +584 -0
  440. package/src/tooling/portable/schema/fixtures/tiinex.root.v1.machine-shape.contract-fixture.md +44 -0
  441. package/src/tooling/portable/schema/fixtures/tiinex.root.v1.ordinary-target.contract-fixture.md +630 -0
  442. package/src/tooling/portable/schema/fixtures/tiinex.schema.contract.v1.repeated-record.contract-fixture.md +69 -0
  443. package/src/tooling/portable/schema/fixtures/tiinex.schema.generation.v1.repeated-record.contract-fixture.md +98 -0
  444. package/src/tooling/portable/schema/fixtures/tiinex.schema.inheritance.v1.repeated-record.contract-fixture.md +58 -0
  445. package/src/tooling/portable/schema/fixtures/tiinex.task.v1.contract-fixture.md +165 -0
  446. package/src/tooling/portable/schema/fixtures/tiinex.transition.definition.v1.contract-fixture.md +744 -0
  447. package/src/tooling/portable/schema/fixtures/tiinex.transition.definition.v1.field-domain.contract-fixture.md +319 -0
  448. package/src/transitions/canonical-schema-cache/053d46ce082d4ec261b82abc44ecca403d61e240/tiinex.relation.v1.schema.md +240 -0
  449. package/src/transitions/canonical-schema-cache/053d46ce082d4ec261b82abc44ecca403d61e240/tiinex.schema.contract.v1.schema.md +391 -0
  450. package/src/transitions/canonical-schema-cache/053d46ce082d4ec261b82abc44ecca403d61e240/tiinex.schema.generation.v1.schema.md +379 -0
  451. package/src/transitions/canonical-schema-cache/053d46ce082d4ec261b82abc44ecca403d61e240/tiinex.schema.transition.companion.v1.schema.md +265 -0
  452. package/src/transitions/canonical-schema-cache/053d46ce082d4ec261b82abc44ecca403d61e240/tiinex.semantic.package.v1.schema.md +402 -0
  453. package/src/transitions/canonical-schema-cache/52ecdea0a75893882ce282214d155f70e1309c2a/tiinex.topic.v1.schema.md +197 -0
  454. package/src/transitions/canonical-schema-cache/d69b8ff55a56b8cb9282b8684db6a938a4435b94/tiinex.root.v1.schema.md +797 -0
  455. package/src/transitions/canonical-schema-cache/d69b8ff55a56b8cb9282b8684db6a938a4435b94/tiinex.transition.definition.v1.schema.md +1062 -0
  456. package/src/transitions/canonicalTransition.packageContracts.js +22 -0
  457. package/src/transitions/canonicalTransition.productDefaults.js +105 -0
  458. package/src/transitions/canonicalTransition.schemaCache.case.mjs +45 -0
  459. package/src/transitions/canonicalTransition.schemaCache.js +81 -0
  460. package/src/transitions/canonicalTransition.semanticPackage.js +146 -0
  461. package/src/transitions/record.transitions.case.mjs +88 -0
  462. package/src/transitions/transition.authorityRepresentation.js +55 -0
  463. package/src/transitions/transition.availabilityPlanner.js +376 -0
  464. package/src/transitions/transition.availabilitySemantics.js +150 -0
  465. package/src/transitions/transition.definitionReadProjection.js +107 -0
  466. package/src/transitions/transition.definitionRegistry.js +355 -0
  467. package/src/transitions/transition.definitionRegistryAggregation.js +21 -0
  468. package/src/transitions/transition.definitions.js +46 -0
  469. package/src/transitions/transition.explicitGenerationPlanner.js +58 -0
  470. package/src/transitions/transition.generationInputPlanner.js +19 -0
  471. package/src/transitions/transition.interpretationMaterializer.js +2 -0
  472. package/src/transitions/transition.invocationBindingPacket.js +52 -0
  473. package/src/transitions/transition.invocationBindingPlanner.js +467 -0
  474. package/src/transitions/transition.legacyShorthand.js +133 -0
  475. package/src/transitions/transition.localArtifactMaterializers.js +16 -0
  476. package/src/transitions/transition.localCreateCapability.js +111 -0
  477. package/src/transitions/transition.outputMaterializationPlanner.js +336 -0
  478. package/src/transitions/transition.parentReference.js +90 -0
  479. package/src/transitions/transition.presentation.js +43 -0
  480. package/src/transitions/transition.productPreparation.js +377 -0
  481. package/src/transitions/transition.productPresentation.browser.js +28 -0
  482. package/src/transitions/transition.productPresentation.js +55 -0
  483. package/src/transitions/transition.referenceCreateCapability.js +119 -0
  484. package/src/transitions/transition.referenceTarget.js +50 -0
  485. package/src/transitions/transition.relationMaterializer.js +2 -0
  486. package/src/transitions/transition.resultSemantics.js +453 -0
  487. package/src/transitions/transition.taskMaterializer.js +2 -0
  488. package/src/transitions/transition.topicMaterializer.js +2 -0
  489. package/src/transitions/transition.validate.case.mjs +58 -0
  490. package/src/ui/cards/ArtifactCard.js +1 -0
  491. package/src/ui/cards/SchemaCard.js +1 -0
  492. package/src/ui/cards/SourceCard.js +1 -0
  493. package/src/ui/dialog.presenter.js +24 -0
  494. package/src/ui/feedback/DegradedNotice.js +1 -0
  495. package/src/ui/feedback/EmptyState.js +1 -0
  496. package/src/ui/feedback/ValidationNotice.js +1 -0
  497. package/src/ui/forms/FormRenderer.js +1 -0
  498. package/src/ui/forms/fields/MarkdownField.js +1 -0
  499. package/src/ui/forms/fields/SelectField.js +1 -0
  500. package/src/ui/forms/fields/SourcePickerField.js +1 -0
  501. package/src/ui/forms/fields/TextField.js +1 -0
  502. package/src/ui/icon.paths.js +37 -0
  503. package/src/ui/layout/Panel.js +1 -0
  504. package/src/ui/layout/ResponsiveFrame.js +1 -0
  505. package/src/ui/layout/SplitView.js +1 -0
  506. package/src/ui/layout/Stack.js +1 -0
  507. package/src/ui/panels/DetailPanel.js +1 -0
  508. package/src/ui/panels/LineagePanel.js +1 -0
  509. package/src/ui/panels/SourcePanel.js +1 -0
  510. package/src/ui/primitives/Badge.js +1 -0
  511. package/src/ui/primitives/Badge.jsx +5 -0
  512. package/src/ui/primitives/Button.js +1 -0
  513. package/src/ui/primitives/Button.jsx +12 -0
  514. package/src/ui/primitives/Card.js +1 -0
  515. package/src/ui/primitives/Field.js +1 -0
  516. package/src/ui/primitives/Field.jsx +21 -0
  517. package/src/ui/primitives/Icon.jsx +90 -0
  518. package/src/ui/primitives/Modal.jsx +27 -0
  519. package/src/validation/i18n.js +37 -0
  520. package/src/validation/validateArtifact.case.mjs +41 -0
  521. package/src/verses/column/ColumnVerse.js +1 -0
  522. package/src/verses/column/column.model.js +6 -0
  523. package/src/verses/column/column.project.js +7 -0
  524. package/src/verses/context.js +26 -0
  525. package/src/verses/contracts.js +1 -0
  526. package/src/verses/hostRegistry.js +47 -0
  527. package/src/verses/registry.js +22 -0
  528. package/src/verses/universe.model.js +8 -0
  529. package/src/verses/universe.presenter.js +8 -0
  530. package/src/verses/universe.project.js +12 -0
  531. package/src/viewer.js +34 -0
  532. package/src/workspace.model.js +5 -0
  533. package/src/workspaces/workspace.actions.js +1 -0
  534. package/src/workspaces/workspace.artifactActions.js +56 -0
  535. package/src/workspaces/workspace.auditView.js +153 -0
  536. package/src/workspaces/workspace.candidates.js +149 -0
  537. package/src/workspaces/workspace.config.js +153 -0
  538. package/src/workspaces/workspace.configuredSource.js +68 -0
  539. package/src/workspaces/workspace.discoveryProgress.js +34 -0
  540. package/src/workspaces/workspace.discoveryView.js +386 -0
  541. package/src/workspaces/workspace.displayFilters.js +63 -0
  542. package/src/workspaces/workspace.displayOptions.js +58 -0
  543. package/src/workspaces/workspace.emptyStateCopy.js +24 -0
  544. package/src/workspaces/workspace.entrypointLifecycle.js +131 -0
  545. package/src/workspaces/workspace.entrypointViewIntent.js +83 -0
  546. package/src/workspaces/workspace.entrypoints.js +157 -0
  547. package/src/workspaces/workspace.feedSort.js +152 -0
  548. package/src/workspaces/workspace.import.js +212 -0
  549. package/src/workspaces/workspace.importConflicts.js +200 -0
  550. package/src/workspaces/workspace.importLifecycle.case.mjs +103 -0
  551. package/src/workspaces/workspace.lifecycle.case.mjs +353 -0
  552. package/src/workspaces/workspace.lifecycle.js +357 -0
  553. package/src/workspaces/workspace.lineageView.js +244 -0
  554. package/src/workspaces/workspace.localSourceLifecycle.js +91 -0
  555. package/src/workspaces/workspace.materialLedger.js +110 -0
  556. package/src/workspaces/workspace.materialReconciliation.case.mjs +164 -0
  557. package/src/workspaces/workspace.materialReconciliation.js +390 -0
  558. package/src/workspaces/workspace.memberIdentity.js +140 -0
  559. package/src/workspaces/workspace.model.js +2 -0
  560. package/src/workspaces/workspace.navigation.js +20 -0
  561. package/src/workspaces/workspace.openSemantics.js +33 -0
  562. package/src/workspaces/workspace.pathTree.js +132 -0
  563. package/src/workspaces/workspace.persistence.case.mjs +271 -0
  564. package/src/workspaces/workspace.persistence.js +469 -0
  565. package/src/workspaces/workspace.persistenceCache.js +41 -0
  566. package/src/workspaces/workspace.persistenceClear.js +45 -0
  567. package/src/workspaces/workspace.persistencePresentation.js +93 -0
  568. package/src/workspaces/workspace.persistenceRecovery.js +147 -0
  569. package/src/workspaces/workspace.persistenceRouteCache.js +82 -0
  570. package/src/workspaces/workspace.publicationReceipts.case.mjs +36 -0
  571. package/src/workspaces/workspace.publicationReceipts.js +56 -0
  572. package/src/workspaces/workspace.recoverabilityView.js +264 -0
  573. package/src/workspaces/workspace.route.js +432 -0
  574. package/src/workspaces/workspace.runtimeCanonical.js +18 -0
  575. package/src/workspaces/workspace.sourceMaterial.js +71 -0
  576. package/src/workspaces/workspace.sourceRecordPath.js +5 -0
  577. package/src/workspaces/workspace.sourceRecords.js +75 -0
  578. package/src/workspaces/workspace.store.js +3 -0
  579. package/src/workspaces/workspace.summary.js +116 -0
  580. package/src/workspaces/workspace.timePortal.js +137 -0
@@ -0,0 +1,132 @@
1
+ import { inferRecordMaterialRole, MaterialRole } from '@tiinex/core/workspaces/workspace.materialRole.js';
2
+ import { buildRecordLogicalPathMap, recordLogicalPathFromMap, normalizeWorkspacePath } from '@tiinex/core/workspaces/workspace.recordPaths.js';
3
+
4
+ const ITEM_TYPE_ORDER = { record: 0, asset: 1 };
5
+
6
+ export function normalizeTreePath(value = '') {
7
+ return normalizeWorkspacePath(value);
8
+ }
9
+
10
+ export function buildWorkspacePathTree(input = {}) {
11
+ const records = Array.isArray(input.records) ? input.records : [];
12
+ const assets = Array.isArray(input.assets) ? input.assets : [];
13
+ const query = String(input.query || '').trim();
14
+ const root = makeFolderNode('', '', 0);
15
+ const recordPathMap = buildRecordLogicalPathMap(records);
16
+
17
+ for (const record of records) {
18
+ addItem(root, makeTreeItem('record', record, recordPathMap));
19
+ }
20
+ for (const asset of assets) {
21
+ addItem(root, makeTreeItem('asset', asset));
22
+ }
23
+
24
+ finalizeFolder(root);
25
+ return {
26
+ schema: 'tiinex.workspace.pathTree.v1',
27
+ rootLabel: input.rootLabel || 'Root',
28
+ query,
29
+ counts: root.counts,
30
+ folders: root.folders,
31
+ items: root.items,
32
+ empty: !root.folders.length && !root.items.length
33
+ };
34
+ }
35
+
36
+ function makeTreeItem(type, source = {}, recordPathMap = null) {
37
+ const path = normalizeTreePath(type === 'record' ? recordLogicalPathFromMap(source, recordPathMap) : (source.path || source.name || source.title || type));
38
+ const fallbackName = type === 'asset' ? 'Asset' : 'Artifact';
39
+ const name = basename(path) || source.title || source.name || fallbackName;
40
+ return {
41
+ type,
42
+ id: source.id || `${type}:${path || name}`,
43
+ path,
44
+ name,
45
+ title: source.title || source.name || name,
46
+ kind: source.kind || source.schema || source.type || type,
47
+ previewState: source.previewState || '',
48
+ materialRole: type === 'record' ? inferRecordMaterialRole(source) : type === 'asset' ? MaterialRole.asset : '',
49
+ source
50
+ };
51
+ }
52
+
53
+ function addItem(root, item) {
54
+ const path = item.path || item.name;
55
+ const parts = normalizeTreePath(path).split('/').filter(Boolean);
56
+ const fileName = parts.pop() || item.name || item.title;
57
+ let folder = root;
58
+ for (const segment of parts) {
59
+ folder = ensureFolder(folder, segment);
60
+ }
61
+ folder.items.push(Object.assign({}, item, { name: fileName || item.name }));
62
+ }
63
+
64
+ function ensureFolder(parent, name) {
65
+ const cleanName = String(name || '').trim() || 'folder';
66
+ let folder = parent._folderMap.get(cleanName);
67
+ if (!folder) {
68
+ const path = parent.path ? `${parent.path}/${cleanName}` : cleanName;
69
+ folder = makeFolderNode(cleanName, path, parent.depth + 1);
70
+ parent._folderMap.set(cleanName, folder);
71
+ parent.folders.push(folder);
72
+ }
73
+ return folder;
74
+ }
75
+
76
+ function makeFolderNode(name, path, depth) {
77
+ return {
78
+ type: 'folder',
79
+ name,
80
+ path,
81
+ depth,
82
+ folders: [],
83
+ items: [],
84
+ counts: { records: 0, leaves: 0, supporting: 0, schemaDefinitions: 0, assets: 0, workspaceArtifacts: 0, total: 0 },
85
+ _folderMap: new Map()
86
+ };
87
+ }
88
+
89
+ function finalizeFolder(folder) {
90
+ const counts = { records: 0, leaves: 0, supporting: 0, schemaDefinitions: 0, assets: 0, workspaceArtifacts: 0, total: 0 };
91
+ for (const child of folder.folders) {
92
+ finalizeFolder(child);
93
+ counts.records += child.counts.records;
94
+ counts.assets += child.counts.assets;
95
+ counts.leaves += child.counts.leaves || 0;
96
+ counts.supporting += child.counts.supporting || 0;
97
+ counts.schemaDefinitions += child.counts.schemaDefinitions || 0;
98
+ counts.workspaceArtifacts += child.counts.workspaceArtifacts;
99
+ counts.total += child.counts.total;
100
+ }
101
+ for (const item of folder.items) {
102
+ if (item.type === 'asset') counts.assets += 1;
103
+ else {
104
+ counts.records += 1;
105
+ const role = item.materialRole || inferRecordMaterialRole(item.source || {});
106
+ if (role === MaterialRole.workspaceArtifact) counts.workspaceArtifacts += 1;
107
+ if (role === MaterialRole.leaf) counts.leaves += 1;
108
+ else if (role === MaterialRole.schemaDefinition) counts.schemaDefinitions += 1;
109
+ else if (role !== MaterialRole.workspaceArtifact) counts.supporting += 1;
110
+ }
111
+ counts.total += 1;
112
+ }
113
+ folder.counts = counts;
114
+ folder.folders.sort(compareFolders);
115
+ folder.items.sort(compareItems);
116
+ delete folder._folderMap;
117
+ }
118
+
119
+ function compareFolders(a, b) {
120
+ return String(a.name || '').localeCompare(String(b.name || ''), undefined, { numeric: true, sensitivity: 'base' });
121
+ }
122
+
123
+ function compareItems(a, b) {
124
+ const typeDelta = (ITEM_TYPE_ORDER[a.type] ?? 9) - (ITEM_TYPE_ORDER[b.type] ?? 9);
125
+ if (typeDelta) return typeDelta;
126
+ return String(a.name || a.title || '').localeCompare(String(b.name || b.title || ''), undefined, { numeric: true, sensitivity: 'base' });
127
+ }
128
+
129
+ function basename(path = '') {
130
+ const parts = String(path || '').split('/').filter(Boolean);
131
+ return parts[parts.length - 1] || '';
132
+ }
@@ -0,0 +1,271 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import vm from 'node:vm';
3
+ import { runExplicitUrlMaterialImportCommand } from '../app/urlMaterialCommand.js';
4
+ await import('./workspace.lifecycle.js');
5
+
6
+ function loadPersistence(options = {}) {
7
+ const storageMap = new Map();
8
+ const historyUrls = [];
9
+ const failKeys = new Set(options.failKeys || []);
10
+ const events = [];
11
+ const sandbox = {
12
+ Buffer,
13
+ window: {
14
+ localStorage: {
15
+ getItem: (key) => storageMap.get(key) || null,
16
+ setItem: (key, value) => {
17
+ if (failKeys.has(key)) throw new Error(`quota:${key}`);
18
+ storageMap.set(key, String(value));
19
+ },
20
+ removeItem: (key) => storageMap.delete(key)
21
+ },
22
+ location: { pathname: '/index.html', search: '', hash: '' },
23
+ CustomEvent: class CustomEvent { constructor(type, options = {}) { this.type = type; this.detail = options.detail; } },
24
+ dispatchEvent: (event) => { events.push(event); return true; },
25
+ history: {
26
+ replaceState: (_a, _b, url) => { historyUrls.push(['replace', url]); sandbox.window.location.hash = url.includes('#') ? `#${url.split('#').pop()}` : ''; },
27
+ pushState: (_a, _b, url) => { historyUrls.push(['push', url]); sandbox.window.location.hash = url.includes('#') ? `#${url.split('#').pop()}` : ''; }
28
+ }
29
+ },
30
+ globalThis: {}
31
+ };
32
+ sandbox.globalThis = sandbox.window;
33
+ vm.createContext(sandbox);
34
+ vm.runInContext(readFileSync(new URL('./workspace.route.js', import.meta.url), 'utf8'), sandbox);
35
+ vm.runInContext(readFileSync(new URL('./workspace.persistenceRecovery.js', import.meta.url), 'utf8'), sandbox);
36
+ vm.runInContext(readFileSync(new URL('./workspace.persistenceRouteCache.js', import.meta.url), 'utf8'), sandbox);
37
+ vm.runInContext(readFileSync(new URL('./workspace.persistencePresentation.js', import.meta.url), 'utf8'), sandbox);
38
+ vm.runInContext(readFileSync(new URL('./workspace.persistenceClear.js', import.meta.url), 'utf8'), sandbox);
39
+ vm.runInContext(readFileSync(new URL('./workspace.persistence.js', import.meta.url), 'utf8'), sandbox);
40
+ return { persistence: sandbox.window.TiinexWorkspacePersistence, routeCache: sandbox.window.TiinexWorkspacePersistenceRouteCache, env: sandbox.window, historyUrls, storageMap, failKeys, events };
41
+ }
42
+
43
+ const { persistence, env, historyUrls, storageMap } = loadPersistence();
44
+ const localSource = { id: 'local', kind: 'local-session', adapterId: 'local', sourceKind: 'local.session', label: 'Local' };
45
+ const githubSource = { id: 'github:docs', kind: 'github-tree', adapterId: 'github', sourceKind: 'github.repo', label: 'Tiinex/docs', repo: 'Tiinex/docs', ref: 'abcdef', rootPath: '.topics' };
46
+ const state = {
47
+ version: 1,
48
+ activeWorkspaceId: 'workspace:embedded-default:tiinex-docs',
49
+ view: { workspaceVerse: 'tree', query: 'topic' },
50
+ workspaces: [{
51
+ id: 'workspace:embedded-default:tiinex-docs', name: 'Tiinex docs', title: 'Tiinex docs', sources: [localSource, githubSource], sourceOrder: ['local', githubSource.id],
52
+ records: [
53
+ { id: 'local:r1', title: 'Local Topic', path: 'topics/local.md', markdown: '# Local Topic', sourceMode: 'local-draft', source: localSource },
54
+ { id: 'source:r2', title: 'Remote Topic', path: 'topics/remote.md', markdown: '# Remote must not be durable local authority', sourceMode: 'source-backed', source: githubSource, materialRole: 'leaf' }
55
+ ],
56
+ assets: [
57
+ { id: 'local:a1', path: 'assets/local.svg', name: 'local.svg', content: '<svg/>', previewState: 'available', source: localSource, publicAvailability: 'not-public' },
58
+ { id: 'source:a2', path: 'assets/remote.svg', name: 'remote.svg', content: '<svg>remote</svg>', previewState: 'available', source: githubSource }
59
+ ], importLog: [{ kind: 'fixture', at: '2026-08-11T00:00:00.000Z' }]
60
+ }]
61
+ };
62
+
63
+ if (persistence.readInitialState({ storage: env.localStorage, location: env.location }) !== null) throw new Error('clean URL must not restore unrelated persisted route/session state');
64
+ const cleanResolution = persistence.resolveInitialState({ storage: env.localStorage, location: env.location });
65
+ if (cleanResolution.requested || cleanResolution.resolved) throw new Error('clean URL must remain unowned by explicit route resolution');
66
+ env.location.hash = '#state=%%%invalid%%%';
67
+ const malformedResolution = persistence.resolveInitialState({ storage: env.localStorage, location: env.location });
68
+ if (!malformedResolution.requested || malformedResolution.resolved || malformedResolution.reason !== 'route-decode-failed') throw new Error('malformed route encoding must be requested but unresolved');
69
+ env.location.hash = `#state=${persistence.encodeState({ arbitrary: true })}`;
70
+ const invalidShapeResolution = persistence.resolveInitialState({ storage: env.localStorage, location: env.location });
71
+ if (!invalidShapeResolution.requested || invalidShapeResolution.resolved) throw new Error('parseable JSON with invalid route shape must not own startup');
72
+ env.location.hash = `#state=${persistence.encodeState({ v: 999, workspaces: [] })}`;
73
+ const unsupportedResolution = persistence.resolveInitialState({ storage: env.localStorage, location: env.location });
74
+ if (unsupportedResolution.resolved || unsupportedResolution.reason !== 'route-version-unsupported') throw new Error('unsupported route version must not own startup');
75
+ env.location.hash = `#state=${persistence.encodeState({ v: 2, activeWorkspaceId: '', view: {}, workspaces: [] })}`;
76
+ const explicitEmptyResolution = persistence.resolveInitialState({ storage: env.localStorage, location: env.location });
77
+ if (!explicitEmptyResolution.resolved || explicitEmptyResolution.reason !== 'route-owned-empty' || !Array.isArray(explicitEmptyResolution.state?.workspaces) || explicitEmptyResolution.state.workspaces.length !== 0) throw new Error('well-shaped v2 empty route must remain a legitimate explicit route owner');
78
+ env.location.hash = '';
79
+ const hash = persistence.writeState(state, { storage: env.localStorage, location: env.location, history: env.history, mode: 'push' });
80
+ if (!hash.startsWith('#state=')) throw new Error('explicit route must be written to hash');
81
+ if (historyUrls[0]?.[0] !== 'push') throw new Error('route creation should support push history');
82
+ if (!storageMap.has(persistence.STORAGE_KEY)) throw new Error('route/source cache must use its own storage domain');
83
+ if (!storageMap.has(persistence.LOCAL_DELTA_KEY)) throw new Error('durable local deltas must use a separate storage domain');
84
+ if (!storageMap.has(persistence.LOCAL_RECOVERY_INDEX_KEY)) throw new Error('durable local deltas must publish a small recovery index so clean-start local work cannot become orphaned');
85
+ if (storageMap.has(persistence.LEGACY_STORAGE_KEY)) throw new Error('new writes must not use legacy all-in-one workspaceState key');
86
+ const writtenRoute = persistence.decodeState(hash);
87
+ const writtenRemoteShell = writtenRoute.workspaces[0].records.find((record) => record.id === 'source:r2');
88
+ const writtenRemoteAssetShell = writtenRoute.workspaces[0].assets.find((asset) => asset.id === 'source:a2');
89
+ if (writtenRemoteShell?.markdown !== '' || writtenRemoteShell?.materialAvailability !== 'material-unavailable' || writtenRemoteShell?.cacheState !== 'route-shell-material-unavailable') throw new Error('makeRouteState/writeState must truthfully mark omitted source record material unavailable');
90
+ if (writtenRemoteAssetShell?.content || writtenRemoteAssetShell?.dataUrl || writtenRemoteAssetShell?.materialAvailability !== 'material-unavailable' || writtenRemoteAssetShell?.previewState === 'available' || writtenRemoteAssetShell?.cacheState !== 'route-shell-material-unavailable') throw new Error('makeRouteState/writeState must truthfully mark omitted source asset material unavailable');
91
+
92
+ const routeCacheText = storageMap.get(persistence.STORAGE_KEY) || '';
93
+ const localDeltaText = storageMap.get(persistence.LOCAL_DELTA_KEY) || '';
94
+ if (routeCacheText.includes('# Remote must not be durable local authority')) throw new Error('source-backed markdown must not be persisted as localStorage authority');
95
+ if (!localDeltaText.includes('# Local Topic')) throw new Error('local draft markdown must be preserved in durable local delta storage');
96
+ if (!localDeltaText.includes('<svg/>')) throw new Error('local asset availability must be preserved in local delta storage');
97
+ if (localDeltaText.includes('# Remote must not be durable local authority')) throw new Error('source-backed markdown must not leak into local delta storage');
98
+
99
+ const restored = persistence.readInitialState({ storage: env.localStorage, location: env.location });
100
+ const restoredWorkspace = restored.workspaces[0];
101
+ const restoredLocal = restoredWorkspace.records.find((record) => record.id === 'local:r1');
102
+ const restoredRemote = restoredWorkspace.records.find((record) => record.id === 'source:r2');
103
+ if (restoredLocal?.markdown !== '# Local Topic') throw new Error('explicit route restore must reapply durable local delta markdown');
104
+ if (restoredRemote?.markdown !== '') throw new Error('source-backed body must restore as metadata/material-unavailable until transport reloads it');
105
+ if (restoredRemote?.cacheState !== 'source-backed-metadata-only-session-cache') throw new Error('source-backed route cache must disclose metadata-only state');
106
+ if (restoredWorkspace.assets.find((asset) => asset.id === 'local:a1')?.content !== '<svg/>') throw new Error('local asset must restore from local delta storage');
107
+ if (restoredWorkspace.assets.find((asset) => asset.id === 'source:a2')?.content) throw new Error('source-backed asset body must remain metadata-only');
108
+
109
+ // Clean/default startup may bootstrap a stable workspace first, then reapply only
110
+ // its browser-local deltas. This is distinct from blindly restoring old route state.
111
+ const cleanBootstrapState = {
112
+ version: 1, activeWorkspaceId: state.activeWorkspaceId, view: { workspaceVerse: 'feed' },
113
+ workspaces: [{ id: state.activeWorkspaceId, name: 'Tiinex docs', title: 'Tiinex docs', sources: [githubSource], sourceOrder: [githubSource.id], records: [], assets: [] }]
114
+ };
115
+ const hydratedBootstrap = persistence.hydrateWorkspaceWithLocalDeltas(cleanBootstrapState, state.activeWorkspaceId, env.localStorage);
116
+ if (!hydratedBootstrap.workspaces[0].records.some((record) => record.id === 'local:r1' && record.markdown === '# Local Topic')) throw new Error('default bootstrap must be able to reapply saved local deltas after config ownership is established');
117
+ if (hydratedBootstrap.workspaces[0].records.some((record) => record.id === 'source:r2')) throw new Error('local delta hydration must not inject source cache records');
118
+ const unrelatedLocalState = { ...state, activeWorkspaceId: 'workspace:local-only', workspaces: [{ id: 'workspace:local-only', name: 'Local only', records: [{ id: 'local:standalone', path: 'draft.md', markdown: '# Draft', sourceMode: 'local-draft', source: localSource }], assets: [], sources: [localSource], sourceOrder: ['local'] }] };
119
+ const standaloneEnv = loadPersistence();
120
+ standaloneEnv.persistence.writeState(unrelatedLocalState, { storage: standaloneEnv.env.localStorage, location: standaloneEnv.env.location, history: standaloneEnv.env.history });
121
+ const canonicalBase = { version: 1, activeWorkspaceId: state.activeWorkspaceId, view: { workspaceVerse: 'feed' }, workspaces: [{ id: state.activeWorkspaceId, name: 'Tiinex docs', records: [], assets: [], sources: [githubSource], sourceOrder: [githubSource.id] }] };
122
+ const augmented = standaloneEnv.persistence.augmentStartupStateWithLocalRecovery(canonicalBase, standaloneEnv.env.localStorage);
123
+ if (augmented.activeWorkspaceId !== 'workspace:local-only') throw new Error('saved local focus should restore after canonical config/default workspace ownership is established');
124
+ if (!augmented.workspaces.some((workspace) => workspace.id === 'workspace:local-only')) throw new Error('unmatched durable local workspace must remain explicitly recoverable alongside canonical startup');
125
+ if (!augmented.workspaces.some((workspace) => workspace.id === state.activeWorkspaceId && workspace.sources?.some((source) => source.id === githubSource.id))) throw new Error('restored local focus must not replace canonical source/bootstrap authority');
126
+
127
+ env.location.hash = '';
128
+ if (persistence.readInitialState({ storage: env.localStorage, location: env.location }) !== null) throw new Error('clean URL remains isolated from stale route cache; bootstrap owns startup');
129
+ const recoverableLocal = persistence.readRecoverableLocalState(env.localStorage);
130
+ if (!recoverableLocal?.workspaces?.some((workspace) => workspace.id === state.activeWorkspaceId)) throw new Error('durable browser-local work must remain discoverable after route/hash loss');
131
+ if (!recoverableLocal.workspaces[0].records.some((record) => record.id === 'local:r1')) throw new Error('clean-start recovery must preserve local draft payload');
132
+ if (recoverableLocal.workspaces[0].records.some((record) => record.id === 'source:r2')) throw new Error('clean-start local recovery must not resurrect source-cache material as local truth');
133
+ if (recoverableLocal.workspaces[0].workspaceRecovery?.recoveredFromCleanStart !== true) throw new Error('clean-start recovery must disclose its boundary');
134
+
135
+ // Hash-only public/share routes work without local cache and retain honest source shells.
136
+ const noCache = loadPersistence();
137
+ const routeOnly = noCache.persistence.encodeState({
138
+ v: 2, activeWorkspaceId: 'shared-w', view: { workspaceVerse: 'feed' },
139
+ workspaces: [{ id: 'shared-w', name: 'Shared', sources: [githubSource], sourceOrder: [githubSource.id], records: [{ id: 'source:shared', title: 'Remote shell', path: 'topics/remote.md', sourceMode: 'source-backed', source: githubSource, cacheState: 'route-shell-material-unavailable', materialAvailability: 'material-unavailable' }], assets: [] }]
140
+ });
141
+ noCache.env.location.hash = `#state=${routeOnly}`;
142
+ const publicRestored = noCache.persistence.readInitialState({ storage: noCache.env.localStorage, location: noCache.env.location });
143
+ if (publicRestored.workspaces[0].records[0].source.adapterId !== 'github') throw new Error('public hash route must preserve explicit source boundary');
144
+ if (publicRestored.workspaces[0].records[0].markdown) throw new Error('public hash route must not invent source body cache');
145
+ const publicWithLocal = loadPersistence();
146
+ publicWithLocal.persistence.writeState(unrelatedLocalState, { storage: publicWithLocal.env.localStorage, location: publicWithLocal.env.location, history: publicWithLocal.env.history });
147
+ publicWithLocal.env.location.hash = `#state=${routeOnly}`;
148
+ const isolatedPublic = publicWithLocal.persistence.readInitialState({ storage: publicWithLocal.env.localStorage, location: publicWithLocal.env.location });
149
+ if (isolatedPublic.workspaces.some((workspace) => workspace.id === 'workspace:local-only')) throw new Error('explicit public/share route must remain isolated from unrelated standalone local recovery');
150
+
151
+
152
+ // Explicit route membership is authoritative even when session cache has the same workspace id.
153
+ const membership = loadPersistence();
154
+ const staleSource = { id: 'github:stale', kind: 'github-tree', adapterId: 'github', sourceKind: 'github.repo', label: 'Stale/source', repo: 'Stale/source', ref: 'old', rootPath: '.topics' };
155
+ const routeSource = { id: 'github:route', kind: 'github-tree', adapterId: 'github', sourceKind: 'github.repo', label: 'Route/source', repo: 'Route/source', ref: 'new', rootPath: '.topics' };
156
+ const staleCacheState = {
157
+ version: 1, activeWorkspaceId: 'shared-w', view: { workspaceVerse: 'feed' },
158
+ workspaces: [{ id: 'shared-w', name: 'Cached Shared', sources: [staleSource], sourceOrder: [staleSource.id], records: [{ id: 'source:stale', path: 'topics/stale.md', markdown: '# stale', sourceMode: 'source-backed', source: staleSource }], assets: [{ id: 'asset:stale', path: 'assets/stale.svg', content: '<svg/>', source: staleSource }] }]
159
+ };
160
+ membership.persistence.writeState(staleCacheState, { storage: membership.env.localStorage, location: membership.env.location, history: membership.env.history });
161
+ const authoritativeRouteState = {
162
+ v: 2, activeWorkspaceId: 'shared-w', view: { workspaceVerse: 'feed' },
163
+ workspaces: [{ id: 'shared-w', name: 'Route Shared', sources: [routeSource], sourceOrder: [routeSource.id], records: [{ id: 'source:route', path: 'topics/route.md', markdown: '', sourceMode: 'source-backed', source: routeSource }], assets: [{ id: 'asset:route', path: 'assets/route.svg', content: '', source: routeSource }] }]
164
+ };
165
+ membership.env.location.hash = `#state=${membership.persistence.encodeState(authoritativeRouteState)}`;
166
+ const authoritativeRestored = membership.persistence.readInitialState({ storage: membership.env.localStorage, location: membership.env.location });
167
+ const authoritativeWorkspace = authoritativeRestored.workspaces[0];
168
+ if (authoritativeWorkspace.records.some((record) => record.id === 'source:stale')) throw new Error('session cache must not add stale record membership to explicit route');
169
+ if (!authoritativeWorkspace.records.some((record) => record.id === 'source:route')) throw new Error('explicit route record membership must survive same-id cache hydration');
170
+ if (authoritativeWorkspace.assets.some((asset) => asset.id === 'asset:stale')) throw new Error('session cache must not add stale asset membership to explicit route');
171
+ if (!authoritativeWorkspace.assets.some((asset) => asset.id === 'asset:route')) throw new Error('explicit route asset membership must survive same-id cache hydration');
172
+ if (authoritativeWorkspace.sources.some((source) => source.id === staleSource.id)) throw new Error('session cache must not add stale source membership to explicit route');
173
+ if (authoritativeWorkspace.sources[0]?.id !== routeSource.id || authoritativeWorkspace.sourceOrder.join(',') !== routeSource.id) throw new Error('explicit route source membership/order must remain authoritative');
174
+
175
+ // Cache hydration is monotonic: same-identity metadata cache may enrich gaps but must never downgrade richer explicit route material.
176
+ const richRouteRecord = { id: 'issue:route-snapshot', path: 'issues/42.trace.md', markdown: '# route snapshot', materialAvailability: 'available', cacheState: 'route-issue-snapshot-cache-complete', source: routeSource };
177
+ const weakCachedRecord = { id: richRouteRecord.id, path: richRouteRecord.path, markdown: '', materialAvailability: 'material-unavailable', cacheState: 'source-backed-metadata-only-session-cache', source: routeSource };
178
+ const richRouteAsset = { id: 'asset:route-rich', path: 'assets/route.svg', content: '<svg>route</svg>', previewState: 'available', cacheState: 'route-asset-complete', source: routeSource };
179
+ const weakCachedAsset = { id: richRouteAsset.id, path: richRouteAsset.path, content: '', previewState: 'material-unavailable', cacheState: 'source-backed-metadata-only-session-cache', source: routeSource };
180
+ const monotonic = membership.routeCache.mergeWorkspaceRouteShell(
181
+ { id: 'shared-w', sources: [routeSource], sourceOrder: [routeSource.id], records: [richRouteRecord], assets: [richRouteAsset] },
182
+ { id: 'shared-w', sources: [routeSource], sourceOrder: [routeSource.id], records: [weakCachedRecord], assets: [weakCachedAsset] }
183
+ );
184
+ if (monotonic.records[0].markdown !== '# route snapshot' || monotonic.records[0].materialAvailability !== 'available' || monotonic.records[0].cacheState !== 'route-issue-snapshot-cache-complete') throw new Error('session cache must not downgrade richer route-owned record material/status');
185
+ if (monotonic.assets[0].content !== '<svg>route</svg>' || monotonic.assets[0].previewState !== 'available' || monotonic.assets[0].cacheState !== 'route-asset-complete') throw new Error('session cache must not downgrade richer route-owned asset material/status');
186
+
187
+ // Legacy candidate-bearing route/cache state is migrated once at the I/O boundary.
188
+ const legacy = loadPersistence();
189
+ const legacyState = {
190
+ version: 1, activeWorkspaceId: 'legacy-w', view: { workspaceVerse: 'feed' },
191
+ workspaces: [{ id: 'legacy-w', name: 'Legacy', title: 'Legacy', sources: [githubSource], records: [], assets: [], workspaceMergeCandidates: [{ id: 'legacy-candidate', sourceRecordId: 'source:legacy:workspace', title: 'Legacy Workspace', path: '.topics/legacy.workspace.md', markdown: '# Legacy Workspace', sourceMode: 'source-backed', source: githubSource, materialReconciliation: { status: 'source-candidate-over-local', localCandidateSnapshot: { id: 'local:legacy', title: 'Legacy local edit', path: '.topics/legacy.workspace.md', markdown: '# Legacy Workspace\n\nlocal edit', sourceMode: 'local-workspace-file', source: localSource } } }] }]
192
+ };
193
+ legacy.env.location.hash = `#state=${legacy.persistence.encodeState(legacyState)}`;
194
+ const migratedLegacy = legacy.persistence.readInitialState({ storage: legacy.env.localStorage, location: legacy.env.location });
195
+ const migratedWorkspace = migratedLegacy.workspaces[0];
196
+ if (Object.prototype.hasOwnProperty.call(migratedWorkspace, 'workspaceMergeCandidates')) throw new Error('legacy candidate shape must be consumed at the persistence boundary');
197
+ const migratedCandidateRecord = migratedWorkspace.records.find((record) => record.id === 'source:legacy:workspace');
198
+ if (!migratedCandidateRecord?.workspaceArtifactRole?.migratedFromLegacyCandidate) throw new Error('legacy candidate must become canonical workspace artifact record');
199
+ if (migratedCandidateRecord.workspaceArtifactRole.schema !== 'tiinex.workspace.artifact.role.v1') throw new Error('legacy candidate migration must emit the canonical workspace artifact role schema');
200
+ if (migratedCandidateRecord.workspaceArtifactRole.openEligible !== true || migratedCandidateRecord.workspaceArtifactRole.mergeEligible !== true) throw new Error('legacy candidate migration must emit canonical Open/Merge capability flags');
201
+ if ('openMergeEligible' in migratedCandidateRecord.workspaceArtifactRole) throw new Error('legacy combined Open/Merge flag must not survive canonical migration');
202
+ if (!migratedWorkspace.records.some((record) => record.id === 'local:legacy' && record.materialReconciliation?.status === 'legacy-candidate-local-snapshot-migrated-explicit')) throw new Error('divergent hidden legacy candidate snapshot must migrate into explicit local material');
203
+
204
+ // Quota policy: disposable route cache may be dropped, but local delta failure is surfaced.
205
+ const quota = loadPersistence();
206
+ quota.failKeys.add(quota.persistence.STORAGE_KEY);
207
+ quota.persistence.writeState(state, { storage: quota.env.localStorage, location: quota.env.location, history: quota.env.history });
208
+ if (!quota.storageMap.has(quota.persistence.LOCAL_DELTA_KEY)) throw new Error('local deltas should still persist when disposable route cache cannot be written');
209
+ const localFailure = loadPersistence();
210
+ const oldSavedState = JSON.parse(JSON.stringify(state));
211
+ oldSavedState.workspaces[0].records[0].markdown = '# OLD SAVED WORK';
212
+ localFailure.persistence.writeState(oldSavedState, { storage: localFailure.env.localStorage, location: localFailure.env.location, history: localFailure.env.history });
213
+ const previousDurableDelta = localFailure.storageMap.get(localFailure.persistence.LOCAL_DELTA_KEY);
214
+ const previousRecoveryIndex = localFailure.storageMap.get(localFailure.persistence.LOCAL_RECOVERY_INDEX_KEY);
215
+ const newerUnsavedState = JSON.parse(JSON.stringify(oldSavedState));
216
+ newerUnsavedState.workspaces[0].records[0].markdown = '# NEWER UNSAVED WORK';
217
+ localFailure.failKeys.add(localFailure.persistence.LOCAL_DELTA_KEY);
218
+ localFailure.persistence.writeState(newerUnsavedState, { storage: localFailure.env.localStorage, location: localFailure.env.location, history: localFailure.env.history });
219
+ if (localFailure.storageMap.get(localFailure.persistence.LOCAL_DELTA_KEY) !== previousDurableDelta) throw new Error('failed local delta update must preserve the last-known-good durable snapshot');
220
+ if (localFailure.storageMap.get(localFailure.persistence.LOCAL_RECOVERY_INDEX_KEY) !== previousRecoveryIndex) throw new Error('failed local delta update must preserve the last-known-good recovery index');
221
+ const recoveredAfterFailure = localFailure.persistence.readRecoverableLocalState(localFailure.env.localStorage);
222
+ if (!recoveredAfterFailure?.workspaces?.[0]?.records?.some((record) => record.markdown === '# OLD SAVED WORK')) throw new Error('last-known-good local work must remain readable/recoverable after failed newer save');
223
+ if (recoveredAfterFailure?.workspaces?.[0]?.records?.some((record) => record.markdown === '# NEWER UNSAVED WORK')) throw new Error('failed newer local snapshot must not masquerade as durably persisted');
224
+ if (!localFailure.env.TiinexLocalStatePersistenceFailure?.localMaterialAtRisk || localFailure.env.TiinexLocalStatePersistenceFailure?.newestChangesPersisted !== false) throw new Error('local delta persistence failure must explicitly disclose that newest changes were not persisted');
225
+ if (!localFailure.env.TiinexLocalStatePersistenceFailure?.lastKnownGoodPreserved || !localFailure.env.TiinexLocalStatePersistenceFailure?.recoveryIndexPreserved) throw new Error('persistence failure receipt must disclose preserved last-known-good recovery');
226
+ if (!localFailure.events.some((event) => event.type === 'tiinex:local-persistence-failure' && event.detail?.localMaterialAtRisk && event.detail?.lastKnownGoodPreserved)) throw new Error('local persistence risk must dispatch a user-surfaceable product event with preserved-recovery truth');
227
+
228
+ const preservedClear = loadPersistence();
229
+ preservedClear.persistence.writeState(state, { storage: preservedClear.env.localStorage, location: preservedClear.env.location, history: preservedClear.env.history });
230
+ const preservedDelta = preservedClear.storageMap.get(preservedClear.persistence.LOCAL_DELTA_KEY);
231
+ const preservedIndex = preservedClear.storageMap.get(preservedClear.persistence.LOCAL_RECOVERY_INDEX_KEY);
232
+ preservedClear.env.location.hash = '#public-target-proof';
233
+ preservedClear.persistence.clearState({
234
+ storage: preservedClear.env.localStorage,
235
+ location: preservedClear.env.location,
236
+ history: preservedClear.env.history,
237
+ mode: 'replace',
238
+ preserveUrl: true,
239
+ durableLocalPolicy: 'preserve-existing'
240
+ });
241
+ if (preservedClear.storageMap.has(preservedClear.persistence.STORAGE_KEY)) throw new Error('public-owned empty-state clear may remove current route/session cache');
242
+ if (preservedClear.storageMap.get(preservedClear.persistence.LOCAL_DELTA_KEY) !== preservedDelta) throw new Error('public-owned empty-state clear must preserve durable local delta');
243
+ if (preservedClear.storageMap.get(preservedClear.persistence.LOCAL_RECOVERY_INDEX_KEY) !== preservedIndex) throw new Error('public-owned empty-state clear must preserve local recovery index');
244
+ if (preservedClear.env.location.hash !== '#public-target-proof') throw new Error('public-owned empty-state clear must preserve public route URL');
245
+
246
+ persistence.clearState({ storage: env.localStorage, location: env.location, history: env.history, mode: 'push' });
247
+ if (storageMap.has(persistence.STORAGE_KEY) || storageMap.has(persistence.LOCAL_DELTA_KEY) || storageMap.has(persistence.LOCAL_RECOVERY_INDEX_KEY) || storageMap.has(persistence.LEGACY_STORAGE_KEY)) throw new Error('ordinary explicit clear must remove all persistence domains');
248
+ if (historyUrls.at(-1)?.[0] !== 'push') throw new Error('ordinary closing last workspace should remain push-history capable');
249
+
250
+ console.log('✓ workspace persistence tests passed');
251
+
252
+
253
+ // v426 external-web multi-target persistence: exact target-qualified identities survive local/session reopen.
254
+ const externalLifecycle = globalThis.TiinexWorkspaceLifecycle;
255
+ const externalCreated = externalLifecycle.createWorkspace(externalLifecycle.makeEmptyAppState(), { name: 'External multi persistence' }, { clock: () => '2026-08-18T11:20:00.000Z' });
256
+ const externalUrlA = 'https://a.example.test/folder/001.trace.md';
257
+ const externalUrlB = 'https://b.example.test/other/001.trace.md';
258
+ const externalImported = await runExplicitUrlMaterialImportCommand({
259
+ lifecycle: externalLifecycle, state: externalCreated.state, workspaceId: externalCreated.workspace.id, urls: [externalUrlA, externalUrlB],
260
+ fetchImpl: async (url) => ({ ok: true, status: 200, statusText: 'OK', text: async () => `# Continuity Context\n\n- Envelope Schema: tiinex.root.v1\n- Current\n - Current Schema: tiinex.topic.v1\n - Created At: 2026-08-18 11:20:00\n - Summary: ${url.includes('a.example') ? 'A' : 'B'}\n\n---\n\n# External ${url}\n`, json: async () => ({}) })
261
+ });
262
+ if (!externalImported.ok || externalImported.records.length !== 2) throw new Error('external multi-target fixture must materialize two records');
263
+ const externalBefore = externalImported.state.workspaces[0].records.map((record) => ({ id: record.id, inputTarget: record.sourceTarget?.inputTarget })).sort((a, b) => a.inputTarget.localeCompare(b.inputTarget));
264
+ if (new Set(externalBefore.map((item) => item.id)).size !== 2) throw new Error('external multi-target fixture must have distinct target-qualified ids before persistence');
265
+ const externalEnv = loadPersistence();
266
+ externalEnv.persistence.writeState(externalImported.state, { storage: externalEnv.env.localStorage, location: externalEnv.env.location, history: externalEnv.env.history });
267
+ const externalRestored = externalEnv.persistence.readInitialState({ storage: externalEnv.env.localStorage, location: externalEnv.env.location });
268
+ const externalAfter = externalRestored.workspaces[0].records.map((record) => ({ id: record.id, inputTarget: record.sourceTarget?.inputTarget })).sort((a, b) => a.inputTarget.localeCompare(b.inputTarget));
269
+ if (externalAfter.length !== 2) throw new Error('external multi-target reopen must preserve both records');
270
+ if (JSON.stringify(externalAfter) !== JSON.stringify(externalBefore)) throw new Error('external multi-target reopen must preserve exact target-qualified identities');
271
+ console.log('workspace persistence external multi-target identity roundtrip: ok');