semantifold 0.8.0 → 0.9.0

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.
@@ -0,0 +1,95 @@
1
+ # Transactional generated-artifact publication
2
+
3
+ `GeneratedArtifactPublisher` persists one complete, already-validated project generation without moving filesystem behavior into a frontend or backend. It publishes all targets through one active-generation pointer; it never presents multiple file renames as project-level atomicity.
4
+
5
+ ## Public API
6
+
7
+ ```js
8
+ import {GeneratedArtifactPublisher} from "semantifold"
9
+
10
+ const publisher = new GeneratedArtifactPublisher({
11
+ projectId: "example-project",
12
+ projectRoot: "/workspace/example",
13
+ publicationRoot: "/workspace/example/.semantifold-output",
14
+ sourceRoots: ["/workspace/example/src"]
15
+ })
16
+
17
+ const published = await publisher.publish({
18
+ generationId: "cycle-0001",
19
+ targets: [{
20
+ id: "java-main",
21
+ role: "text",
22
+ artifactSet: generatedJava,
23
+ sourceProjection: "targets/java/source",
24
+ buildProjection: "targets/java/build",
25
+ validators: [async ({sourcePath, buildPath, targetId}) => {
26
+ // Run a caller-owned checker/compiler with exact arguments and these exact paths.
27
+ // Return metadata for every regular file it produced below buildPath.
28
+ return [{
29
+ path: "Main.class",
30
+ mediaType: "application/java-vm",
31
+ role: "compiler-output"
32
+ }]
33
+ }]
34
+ }]
35
+ })
36
+ ```
37
+
38
+ The constructor requires a stable lowercase project ID, one canonical absolute project root, one dedicated canonical publication directory strictly beneath that project root, and at least one canonical absolute source root within the project. Publication and source roots must remain disjoint both lexically and after real-path resolution. Absolute roots use the active host's native separator and reject its non-native separator alias; target-relative artifact and projection paths remain portable POSIX-style paths. The publication directory must be on a filesystem that supports atomic same-directory regular-file replacement and directory synchronization, and must not be changed concurrently by an untrusted process.
39
+
40
+ Each publication request supplies a unique generation ID and a complete ordered target array. A target has its own stable ID, explicit language-neutral `text`, `binary`, or `application` role, immutable `GeneratedArtifactSet`, and disjoint normalized source/build projections. Empty, absolute, dot, dot-dot, separator-alias, duplicate, portable case-fold, nested, manifest-reserved, escaping, and symlink-traversing paths fail before reachability. The class serializes calls made through one instance; callers must ensure there is only one project publication writer across instances and processes.
41
+
42
+ Validators receive one frozen object containing only `targetId`, the exact generation-scoped `sourcePath`, and the exact generation-scoped `buildPath`. The publisher does not grant a project root, publication root, command runner, or general filesystem capability. A validator may create regular files below its build path and must return ordered `{path, mediaType, role}` metadata for every such file. The publisher inventories the complete generation namespace, including all intermediate directories, and allows only the manifest plus declared source/build projections and files. Every projection root and descended directory is checked without following symbolic links before its entries are read. Undeclared siblings or directories, missing files, symlinks, special nodes, hard-linked files, duplicate reported device/inode identities, collisions, invalid metadata, callback failures, or generated-source mutation reject the candidate.
43
+
44
+ ## Owned layout and manifests
45
+
46
+ The publisher owns these reserved entries beneath the caller-supplied root:
47
+
48
+ ```text
49
+ <publication-root>/
50
+ active-generation.json
51
+ generations/
52
+ <generation-id>/
53
+ manifest.json
54
+ <target source projection>/...
55
+ <target build projection>/...
56
+ .semantifold-publication-journal/
57
+ <generation-id>.json
58
+ ```
59
+
60
+ Generation directories are immutable after publication. `SemantifoldGenerationManifest` version 1 records the project and generation identities plus every ordered target ID and language-neutral role, backend target IDs, source/build projections, target metadata, generated artifacts, and validator/compiler outputs. Each generated-artifact record includes its relative path, exact UTF-8 or binary byte length, media type, role, SHA-256 hash, and complete rich/byte/synthetic provenance. Each build-artifact record includes its relative path, exact byte length, media type, language-neutral role, and SHA-256 hash.
61
+
62
+ The publisher synchronizes staged files, validator outputs, the manifest, and candidate directories. It then reads and hashes every referenced file and reconstructs every `GeneratedArtifactSet` from the staged bytes plus persisted provenance. This revalidates mapping filenames, generated content, Source Map projections, byte ranges, roles, target metadata, ordering, and entry identity before the generation can become active. Manifest JSON is deterministic apart from the explicit generation identity and the deterministic input content.
63
+
64
+ `active-generation.json` is a `SemantifoldActiveGeneration` version-1 record containing the project ID, generation ID, and SHA-256 manifest hash. The manifest intentionally contains no timestamps or ambient host paths.
65
+
66
+ ## Commit and reader state machine
67
+
68
+ Publication uses this order:
69
+
70
+ 1. Validate identities, roots, projections, portable collisions, existing state, and the host replacement contract.
71
+ 2. Write and synchronize an ownership journal, then create one unique candidate generation.
72
+ 3. Stage, synchronize, validate, hash, and re-read every generated and build artifact plus the versioned manifest.
73
+ 4. Write and synchronize a sibling temporary active pointer.
74
+ 5. Atomically rename that regular file over `active-generation.json` in the same directory.
75
+ 6. Synchronize the publication-root directory, then clean the no-longer-needed journal and synchronize the journal directory.
76
+
77
+ There is no copy, truncate, live-pointer rewrite, per-target promotion, or source/build directory rename fallback. Before step 5, the prior pointer remains authoritative. Once step 5 succeeds, the new complete generation is the visible authority; a later directory-sync failure is reported as `PUBLICATION_POINTER_FAILED` without deleting that generation. A journal-cleanup failure returns the committed result with `cleanupPending: true` and cannot roll the pointer back.
78
+
79
+ Readers call `resolveActive()`. It reads the pointer exactly once, verifies the selected manifest, complete filesystem inventory, and all referenced bytes/provenance, then validates journal state without consulting the pointer again. It returns frozen `generationPath`, `manifestPath`, manifest, target ID/role plus `sourcePath`/`buildPath` values, and truthful `cleanupPending` state for a committed journal. Malformed or ambiguous journal state fails closed. A reader must retain that resolved result for its whole operation and must not re-read the active pointer between files. Old committed generations remain present because an existing reader may still hold their paths. Retention and garbage collection are deliberately outside Task 038.
80
+
81
+ ## Recovery
82
+
83
+ The journal proves publisher ownership of a candidate and its sibling temporary pointer; it is not the commit mechanism. Before a later valid publication starts, reconciliation first validates any active pointer and selected generation. It then:
84
+
85
+ - removes a journal-proven candidate and temporary pointer when another generation is active or no pointer exists;
86
+ - retains a journal-proven candidate when the active pointer selects it, removing only its stale temporary pointer and journal; and
87
+ - leaves every unjournaled, committed, or otherwise ambiguous generation untouched.
88
+
89
+ Every candidate-directory deletion is followed by synchronization of `generations/`, and every temporary-pointer deletion is followed by synchronization of the publication directory, before the journal may be removed and its own directory synchronized. An interruption therefore cannot durably retain an orphan after durably losing its only ownership proof.
90
+
91
+ Malformed pointers, manifests, hashes, journals, symlinks, special nodes, project mismatches, and ambiguous reserved entries fail closed with `SemantifoldDiagnostic`. Filesystem failures are preserved as `cause`; stable diagnostic details avoid embedding unrelated ambient environment data.
92
+
93
+ The main diagnostic families are `INVALID_PUBLICATION_REQUEST`, `INVALID_PUBLICATION_ROOT`, `INVALID_PUBLICATION_PROJECTION`, `PUBLICATION_SOURCE_OVERLAP`, `PUBLICATION_PATH_COLLISION`, `PUBLICATION_PROJECTION_OVERLAP`, `PUBLICATION_PATH_ESCAPE`, `PUBLICATION_SYMLINK_TRAVERSAL`, `PUBLICATION_HARD_LINK`, `PUBLICATION_GENERATION_INVENTORY_MISMATCH`, `PUBLICATION_GENERATION_VERIFICATION_FAILED`, `PUBLICATION_UNOWNED_CONFLICT`, `PUBLICATION_ATOMIC_REPLACE_UNSUPPORTED`, `PUBLICATION_STAGE_WRITE_FAILED`, `PUBLICATION_STAGE_HASH_MISMATCH`, `PUBLICATION_VALIDATION_FAILED`, `PUBLICATION_POINTER_FAILED`, `PUBLICATION_RECOVERY_FAILED`, `MALFORMED_ACTIVE_GENERATION`, `ACTIVE_GENERATION_MISSING`, and `MALFORMED_GENERATION_MANIFEST`.
94
+
95
+ Task 038 does not load a project manifest, discover sources, derive compiler arguments, start a CLI or watcher, invoke language-specific tools, delete committed generations, or implement retention. Those responsibilities remain in Tasks 039 and later.
@@ -14,7 +14,7 @@ The inspected baseline is `semantifold@0.7.0`, annotated tag `v0.7.0`, commit `8
14
14
  - `package.json` has no `bin` entry. The public API does not persist generated artifacts, load a project manifest, derive compiler arguments from a target, or watch source files.
15
15
  - The only repository file-change watcher is a test helper; there is no product watch coordinator, incremental dependency graph, event coalescing policy, or long-lived compiler owner.
16
16
 
17
- The current system therefore already proves **JavaScript/JSDoc → semantic IR → Java → `javac`** as a one-shot composition. The roadmap below turns that composition into a safe project workflow without moving filesystem or process lifecycle concerns into parsers or backends.
17
+ The released baseline therefore already proves **JavaScript/JSDoc → semantic IR → Java → `javac`** as a one-shot composition. Task 038 now adds the public language-neutral `GeneratedArtifactPublisher`, immutable generation manifests, one atomic active pointer, pointer-once resolution, and journal-bounded recovery described below. Project configuration, target-owned check plans, CLI behavior, and watching remain Tasks 039 and later.
18
18
 
19
19
  ## Product outcome
20
20
 
@@ -45,15 +45,15 @@ A snapshot is immutable for the cycle. Edits arriving during parse/generation/ch
45
45
 
46
46
  Frontends, semantic validation, linking, and backends remain pure with respect to the project filesystem. They consume the immutable snapshot and return complete `GeneratedArtifactSet` values. No frontend or backend starts watchers, writes files, spawns a compiler, or reads undeclared project files.
47
47
 
48
- ### Transactional artifact publication
48
+ ### Transactional artifact publication (Task 038 implemented)
49
49
 
50
- A publisher stages one complete project candidate as a new immutable generation beneath the publisher-owned publication root. Every target's generated artifacts, mappings, and optional compiler outputs live in generation-relative subtrees on the same filesystem. The publisher validates paths, mappings, hashes, collisions, ownership, and manifests before touching committed state.
50
+ `GeneratedArtifactPublisher` stages one complete project candidate as a new immutable generation beneath a dedicated publisher-owned publication directory inside the canonical project root. Every target carries an explicit language-neutral role, and its generated artifacts, mappings, and optional validator/compiler outputs live in generation-relative subtrees on the same filesystem. The publisher validates paths, mappings, hashes, hard-link identities, complete generation inventory, collisions, ownership, and manifests before touching committed state. Its public contract and caller obligations are documented in [transactional generated-artifact publication](generated-artifact-publication.md).
51
51
 
52
52
  When checking is enabled, target checks run against those staged generation subtrees. Only a fully generated and successfully checked project candidate may be published. The generation manifest is the explicit versioned authority for files the publisher created; it never authorizes mutation outside the publication root.
53
53
 
54
54
  Publication commits exactly one small active-generation pointer by writing and syncing a sibling temporary pointer, atomically replacing the prior pointer on the same filesystem, and syncing the parent directory before reporting success. A host filesystem that cannot provide atomic same-directory regular-file replacement is rejected before publication; copying, truncating, or rewriting the live pointer in place is never a fallback. Readers resolve that pointer once and consume all generated/build paths from the referenced immutable generation. Configured generated-source and compiler-output names are logical projections returned from that resolved generation; they are never independent directories, symlinks, or manifests promoted one after another. The same rule covers every target in a multi-target cycle.
55
55
 
56
- An interruption before the pointer replacement leaves the previous generation active; an interruption after it leaves either the old or new complete pointer durably recoverable, never a composite state. A recovery journal may finish cleanup of unpublished candidates and temporary pointer files, but it is not used to claim atomicity across multiple filesystem operations. Publication never removes a committed generation, because a reader may already hold its resolved snapshot; committed-generation retention/garbage collection is a separate explicit policy. A failed candidate is removed without changing the active pointer.
56
+ An interruption before the pointer replacement leaves the previous generation active; an interruption after it leaves either the old or new complete pointer durably recoverable, never a composite state. A recovery journal may finish cleanup of unpublished candidates and temporary pointer files, but it is not used to claim atomicity across multiple filesystem operations. Candidate and temporary-pointer parent directories are synchronized before the proving journal is removed and its directory synchronized. Publication never removes a committed generation, because a reader may already hold its resolved snapshot; committed-generation retention/garbage collection is a separate explicit policy. A failed candidate is removed without changing the active pointer.
57
57
 
58
58
  ### Target check-plan registry
59
59
 
@@ -147,7 +147,7 @@ Binary and application targets use the same generation watcher only after explic
147
147
  046 later binary/application policies
148
148
  ```
149
149
 
150
- Tasks 038–045 are the selected text-language delivery chain. Task 045 is the single terminal acceptance task for that chain. Task 046 is a non-blocking later extension.
150
+ Task 038 is the implemented publication foundation. Tasks 039–045 are the remaining selected text-language delivery chain, with Task 045 as its single terminal acceptance task. Task 046 is a non-blocking later extension.
151
151
 
152
152
  ## Non-goals
153
153
 
package/index.js CHANGED
@@ -16,6 +16,7 @@ export {SemantifoldDiagnostic}
16
16
  export {createCapabilityAuthority} from "./src/semantic/capabilities.js"
17
17
  export {languageCapabilities, supportedLanguages} from "./src/language-registry.js"
18
18
  export {createGeneratedArtifactSet} from "./src/artifacts.js"
19
+ export {GeneratedArtifactPublisher} from "./src/publication.js"
19
20
  export {createStdlibContractRegistry, listStdlibModules, resolveStdlibModule} from "./src/semantic/stdlib.js"
20
21
  export {createStdlibProviderRegistry, listStdlibProviders} from "./src/stdlib-providers.js"
21
22
  export {negotiateStdlibProviders} from "./src/stdlib-negotiation.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semantifold",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Language-neutral semantic code analysis, transformation, generation, and execution toolkit",
5
5
  "type": "module",
6
6
  "main": "build/index.js",