@semiont/make-meaning 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +105 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -170,6 +170,19 @@ const kb = await createKnowledgeBase(eventStore, project, graphDb, logger);
|
|
|
170
170
|
|
|
171
171
|
The EventBus is created by the backend (or script) and passed into `startMakeMeaning()` as a dependency. Make-meaning does not own or encapsulate the EventBus — it is shared across the entire system.
|
|
172
172
|
|
|
173
|
+
### Pure projection validators
|
|
174
|
+
|
|
175
|
+
The dispatcher in [`src/handlers/job-commands.ts`](src/handlers/job-commands.ts) does projection-validated job creation: when a `mark.assist` (linking) or `yield.fromAnnotation` job arrives with `entityTypes`, the dispatcher validates that every tag is registered; when a tagging job arrives with a `schemaId`, the dispatcher resolves it against the registered tag-schema set.
|
|
176
|
+
|
|
177
|
+
Both rules are pure functions in [`src/views/projection-validators.ts`](src/views/projection-validators.ts):
|
|
178
|
+
|
|
179
|
+
- `resolveTagSchema(schemas, schemaId)` → `{ schema } | { error }` — id lookup with the standard "Tag schema not registered" / "tag-annotation requires schemaId" error formats.
|
|
180
|
+
- `validateEntityTypes(registered, requested)` → `{ ok: true } | { ok: false; unknown }` — set membership check that lists the offending tags in caller order.
|
|
181
|
+
|
|
182
|
+
The dispatcher is the I/O shell: read the projection (via the readers in `src/views/`), pass it to the validator (pure), then either stash the resolved value or rethrow as `job:create-failed`. Validator unit tests run in single-digit milliseconds with no filesystem, no event-bus, no mock JobQueue — the dispatcher integration tests in `__tests__/handlers/job-commands.test.ts` keep the wiring covered.
|
|
183
|
+
|
|
184
|
+
This pattern (functional core, imperative shell) is shared with `@semiont/event-sourcing`'s projection reducers; see [`docs/system/PROJECTION-PATTERN.md`](../../docs/system/PROJECTION-PATTERN.md) for the architectural narrative, the full axiom catalog, and guidance for adding new validators.
|
|
185
|
+
|
|
173
186
|
## Documentation
|
|
174
187
|
|
|
175
188
|
- **[Architecture](./docs/architecture.md)** — Actor model, data flow, storage architecture
|
package/dist/index.d.ts
CHANGED
|
@@ -215,6 +215,7 @@ declare function createKnowledgeBase(eventStore: EventStore, project: SemiontPro
|
|
|
215
215
|
* - mark:archive → resource.archived (+ file removal) (resource-scoped, no result event)
|
|
216
216
|
* - mark:unarchive → resource.unarchived (resource-scoped, no result event)
|
|
217
217
|
* - frame:add-entity-type → entitytype.added → frame:entity-type-added / frame:entity-type-add-failed
|
|
218
|
+
* - frame:add-tag-schema → tagschema.added → frame:tag-schema-added / frame:tag-schema-add-failed
|
|
218
219
|
* - mark:update-entity-types → entitytag.added / entitytag.removed
|
|
219
220
|
* - job:start → job.started
|
|
220
221
|
* - job:complete → job.completed
|
|
@@ -247,6 +248,7 @@ declare class Stower {
|
|
|
247
248
|
private handleMarkArchive;
|
|
248
249
|
private handleMarkUnarchive;
|
|
249
250
|
private handleAddEntityType;
|
|
251
|
+
private handleAddTagSchema;
|
|
250
252
|
private handleUpdateEntityTypes;
|
|
251
253
|
private handleJobStart;
|
|
252
254
|
private handleJobComplete;
|
|
@@ -366,6 +368,7 @@ declare class Matcher {
|
|
|
366
368
|
* - browse:annotation-history-requested — annotation event history
|
|
367
369
|
* - browse:referenced-by-requested — find annotations in the KB graph that reference a resource
|
|
368
370
|
* - browse:entity-types-requested — list entity types from the project projection
|
|
371
|
+
* - browse:tag-schemas-requested — list tag schemas from the project projection
|
|
369
372
|
* - browse:directory-requested — list a project directory, merging fs + ViewStorage
|
|
370
373
|
*/
|
|
371
374
|
|
|
@@ -386,6 +389,7 @@ declare class Browser {
|
|
|
386
389
|
private handleBrowseAnnotationHistory;
|
|
387
390
|
private handleReferencedBy;
|
|
388
391
|
private handleEntityTypes;
|
|
392
|
+
private handleTagSchemas;
|
|
389
393
|
private handleBrowseDirectory;
|
|
390
394
|
stop(): Promise<void>;
|
|
391
395
|
}
|
|
@@ -617,7 +621,7 @@ declare function registerAnnotationLookupHandlers(eventBus: EventBus, kb: Knowle
|
|
|
617
621
|
*/
|
|
618
622
|
declare function registerBindUpdateBodyHandler(eventBus: EventBus, parentLogger: Logger): void;
|
|
619
623
|
|
|
620
|
-
declare function registerJobCommandHandlers(eventBus: EventBus, jobQueue: JobQueue, parentLogger: Logger): void;
|
|
624
|
+
declare function registerJobCommandHandlers(eventBus: EventBus, jobQueue: JobQueue, project: SemiontProject, parentLogger: Logger): void;
|
|
621
625
|
|
|
622
626
|
/**
|
|
623
627
|
* Bus command handlers — pure bus-event translators that bridge the
|
|
@@ -636,7 +640,7 @@ declare function registerJobCommandHandlers(eventBus: EventBus, jobQueue: JobQue
|
|
|
636
640
|
* Register all bus command handlers on the make-meaning EventBus. Called
|
|
637
641
|
* during `startMakeMeaning` after the JobQueue and KnowledgeSystem exist.
|
|
638
642
|
*/
|
|
639
|
-
declare function registerBusHandlers(eventBus: EventBus, knowledgeSystem: KnowledgeSystem, jobQueue: JobQueue, logger: Logger): void;
|
|
643
|
+
declare function registerBusHandlers(eventBus: EventBus, knowledgeSystem: KnowledgeSystem, jobQueue: JobQueue, project: SemiontProject, logger: Logger): void;
|
|
640
644
|
|
|
641
645
|
/**
|
|
642
646
|
* Entity Types Bootstrap
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { getGraphDatabase } from '@semiont/graph';
|
|
|
8
8
|
import { WorkingTreeStore, deriveStorageUri, getExtensionForMimeType } from '@semiont/content';
|
|
9
9
|
import { getEntityTypes, DEFAULT_ENTITY_TYPES } from '@semiont/ontology';
|
|
10
10
|
import { promises } from 'fs';
|
|
11
|
-
import * as
|
|
11
|
+
import * as path3 from 'path';
|
|
12
12
|
import { createGzip, createGunzip } from 'zlib';
|
|
13
13
|
import { pipeline, Readable } from 'stream';
|
|
14
14
|
import { promisify } from 'util';
|
|
@@ -11347,6 +11347,7 @@ var Stower = class {
|
|
|
11347
11347
|
pipe("mark:delete", (e) => this.handleMarkDelete(e)),
|
|
11348
11348
|
pipe("mark:update-body", (e) => this.handleMarkUpdateBody(e)),
|
|
11349
11349
|
pipe("frame:add-entity-type", (e) => this.handleAddEntityType(e)),
|
|
11350
|
+
pipe("frame:add-tag-schema", (e) => this.handleAddTagSchema(e)),
|
|
11350
11351
|
pipe("mark:archive", (e) => this.handleMarkArchive(e)),
|
|
11351
11352
|
pipe("mark:unarchive", (e) => this.handleMarkUnarchive(e)),
|
|
11352
11353
|
pipe("mark:update-entity-types", (e) => this.handleUpdateEntityTypes(e)),
|
|
@@ -11634,6 +11635,24 @@ var Stower = class {
|
|
|
11634
11635
|
});
|
|
11635
11636
|
}
|
|
11636
11637
|
}
|
|
11638
|
+
async handleAddTagSchema(event) {
|
|
11639
|
+
if (!event._userId) {
|
|
11640
|
+
throw new Error("frame:add-tag-schema missing _userId (gateway injection)");
|
|
11641
|
+
}
|
|
11642
|
+
try {
|
|
11643
|
+
await this.kb.eventStore.appendEvent({
|
|
11644
|
+
type: "frame:tag-schema-added",
|
|
11645
|
+
userId: userId(event._userId),
|
|
11646
|
+
version: 1,
|
|
11647
|
+
payload: { schema: event.schema }
|
|
11648
|
+
});
|
|
11649
|
+
} catch (error) {
|
|
11650
|
+
this.logger.error("Failed to add tag schema", { schemaId: event.schema?.id, error: errField(error) });
|
|
11651
|
+
this.eventBus.get("frame:tag-schema-add-failed").next({
|
|
11652
|
+
message: error instanceof Error ? error.message : String(error)
|
|
11653
|
+
});
|
|
11654
|
+
}
|
|
11655
|
+
}
|
|
11637
11656
|
async handleUpdateEntityTypes(event) {
|
|
11638
11657
|
if (!event._userId) {
|
|
11639
11658
|
throw new Error("mark:update-entity-types missing _userId (gateway injection)");
|
|
@@ -11721,7 +11740,7 @@ var Stower = class {
|
|
|
11721
11740
|
var import_rxjs5 = __toESM(require_cjs());
|
|
11722
11741
|
var import_operators5 = __toESM(require_operators());
|
|
11723
11742
|
async function readEntityTypesProjection(project) {
|
|
11724
|
-
const entityTypesPath =
|
|
11743
|
+
const entityTypesPath = path3.join(
|
|
11725
11744
|
project.stateDir,
|
|
11726
11745
|
"projections",
|
|
11727
11746
|
"__system__",
|
|
@@ -11738,6 +11757,24 @@ async function readEntityTypesProjection(project) {
|
|
|
11738
11757
|
throw error;
|
|
11739
11758
|
}
|
|
11740
11759
|
}
|
|
11760
|
+
async function readTagSchemasProjection(project) {
|
|
11761
|
+
const tagSchemasPath = path3.join(
|
|
11762
|
+
project.stateDir,
|
|
11763
|
+
"projections",
|
|
11764
|
+
"__system__",
|
|
11765
|
+
"tagschemas.json"
|
|
11766
|
+
);
|
|
11767
|
+
try {
|
|
11768
|
+
const content = await promises.readFile(tagSchemasPath, "utf-8");
|
|
11769
|
+
const projection = JSON.parse(content);
|
|
11770
|
+
return projection.tagSchemas || [];
|
|
11771
|
+
} catch (error) {
|
|
11772
|
+
if (error.code === "ENOENT") {
|
|
11773
|
+
return [];
|
|
11774
|
+
}
|
|
11775
|
+
throw error;
|
|
11776
|
+
}
|
|
11777
|
+
}
|
|
11741
11778
|
|
|
11742
11779
|
// src/browser.ts
|
|
11743
11780
|
var Browser = class {
|
|
@@ -11767,6 +11804,7 @@ var Browser = class {
|
|
|
11767
11804
|
pipe("browse:annotation-history-requested", (e) => this.handleBrowseAnnotationHistory(e)).subscribe({ error: errorHandler }),
|
|
11768
11805
|
pipe("browse:referenced-by-requested", (e) => this.handleReferencedBy(e)).subscribe({ error: errorHandler }),
|
|
11769
11806
|
pipe("browse:entity-types-requested", (e) => this.handleEntityTypes(e)).subscribe({ error: errorHandler }),
|
|
11807
|
+
pipe("browse:tag-schemas-requested", (e) => this.handleTagSchemas(e)).subscribe({ error: errorHandler }),
|
|
11770
11808
|
pipe("browse:directory-requested", (e) => this.handleBrowseDirectory(e)).subscribe({ error: errorHandler })
|
|
11771
11809
|
);
|
|
11772
11810
|
}
|
|
@@ -12011,14 +12049,29 @@ var Browser = class {
|
|
|
12011
12049
|
});
|
|
12012
12050
|
}
|
|
12013
12051
|
}
|
|
12052
|
+
async handleTagSchemas(event) {
|
|
12053
|
+
try {
|
|
12054
|
+
const tagSchemas = await readTagSchemasProjection(this.project);
|
|
12055
|
+
this.eventBus.get("browse:tag-schemas-result").next({
|
|
12056
|
+
correlationId: event.correlationId,
|
|
12057
|
+
response: { tagSchemas }
|
|
12058
|
+
});
|
|
12059
|
+
} catch (error) {
|
|
12060
|
+
this.logger.error("Tag schemas read failed", { error: errField(error) });
|
|
12061
|
+
this.eventBus.get("browse:tag-schemas-failed").next({
|
|
12062
|
+
correlationId: event.correlationId,
|
|
12063
|
+
message: error instanceof Error ? error.message : String(error)
|
|
12064
|
+
});
|
|
12065
|
+
}
|
|
12066
|
+
}
|
|
12014
12067
|
// ========================================================================
|
|
12015
12068
|
// Filesystem read handler
|
|
12016
12069
|
// ========================================================================
|
|
12017
12070
|
async handleBrowseDirectory(event) {
|
|
12018
12071
|
const { correlationId, path: reqPath, sort = "name" } = event;
|
|
12019
12072
|
const projectRoot = this.project.root;
|
|
12020
|
-
const resolved =
|
|
12021
|
-
if (!resolved.startsWith(projectRoot +
|
|
12073
|
+
const resolved = path3.resolve(projectRoot, reqPath);
|
|
12074
|
+
if (!resolved.startsWith(projectRoot + path3.sep) && resolved !== projectRoot) {
|
|
12022
12075
|
this.eventBus.get("browse:directory-failed").next({
|
|
12023
12076
|
correlationId,
|
|
12024
12077
|
path: reqPath,
|
|
@@ -12042,12 +12095,12 @@ var Browser = class {
|
|
|
12042
12095
|
const allViews = await this.views.getAll();
|
|
12043
12096
|
const prefix = `file://${resolved}`;
|
|
12044
12097
|
const viewsByUri = new Map(
|
|
12045
|
-
allViews.filter((v) => v.resource.storageUri?.startsWith(prefix + "/") || v.resource.storageUri?.startsWith(prefix +
|
|
12098
|
+
allViews.filter((v) => v.resource.storageUri?.startsWith(prefix + "/") || v.resource.storageUri?.startsWith(prefix + path3.sep)).map((v) => [v.resource.storageUri, v])
|
|
12046
12099
|
);
|
|
12047
12100
|
const entries = [];
|
|
12048
12101
|
for (const dirent of visible) {
|
|
12049
|
-
const entryPath =
|
|
12050
|
-
const relPath =
|
|
12102
|
+
const entryPath = path3.join(resolved, dirent.name);
|
|
12103
|
+
const relPath = path3.relative(projectRoot, entryPath);
|
|
12051
12104
|
if (dirent.isDirectory()) {
|
|
12052
12105
|
let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
12053
12106
|
try {
|
|
@@ -12585,6 +12638,31 @@ function registerBindUpdateBodyHandler(eventBus, parentLogger) {
|
|
|
12585
12638
|
logger.warn("Bind body-update failed after forwarding", { correlationId: cid, message });
|
|
12586
12639
|
});
|
|
12587
12640
|
}
|
|
12641
|
+
|
|
12642
|
+
// src/views/projection-validators.ts
|
|
12643
|
+
function resolveTagSchema(tagSchemas, schemaId) {
|
|
12644
|
+
if (typeof schemaId !== "string" || !schemaId) {
|
|
12645
|
+
return { error: "tag-annotation requires schemaId" };
|
|
12646
|
+
}
|
|
12647
|
+
const schema = tagSchemas.find((s) => s.id === schemaId);
|
|
12648
|
+
if (!schema) {
|
|
12649
|
+
return { error: `Tag schema not registered: ${schemaId}` };
|
|
12650
|
+
}
|
|
12651
|
+
return { schema };
|
|
12652
|
+
}
|
|
12653
|
+
function validateEntityTypes(registered, requested) {
|
|
12654
|
+
if (!requested || requested.length === 0) {
|
|
12655
|
+
return { ok: true };
|
|
12656
|
+
}
|
|
12657
|
+
const set = new Set(registered);
|
|
12658
|
+
const unknown = requested.filter((t) => !set.has(t));
|
|
12659
|
+
return unknown.length > 0 ? { ok: false, unknown } : { ok: true };
|
|
12660
|
+
}
|
|
12661
|
+
function entityTypesNotRegisteredMessage(unknown) {
|
|
12662
|
+
return `Entity type not registered: ${unknown.join(", ")}`;
|
|
12663
|
+
}
|
|
12664
|
+
|
|
12665
|
+
// src/handlers/job-commands.ts
|
|
12588
12666
|
function parseDidUser(did) {
|
|
12589
12667
|
const parts = did.split(":");
|
|
12590
12668
|
const usersIdx = parts.indexOf("users");
|
|
@@ -12592,7 +12670,7 @@ function parseDidUser(did) {
|
|
|
12592
12670
|
const email = decodeURIComponent(parts.slice(usersIdx + 1).join(":"));
|
|
12593
12671
|
return { userId: did, email, domain };
|
|
12594
12672
|
}
|
|
12595
|
-
function registerJobCommandHandlers(eventBus, jobQueue, parentLogger) {
|
|
12673
|
+
function registerJobCommandHandlers(eventBus, jobQueue, project, parentLogger) {
|
|
12596
12674
|
const logger = parentLogger.child({ component: "job-commands" });
|
|
12597
12675
|
eventBus.get("job:create").subscribe(async (command) => {
|
|
12598
12676
|
const { correlationId, jobType, resourceId: resId, params, _userId } = command;
|
|
@@ -12620,9 +12698,25 @@ function registerJobCommandHandlers(eventBus, jobQueue, parentLogger) {
|
|
|
12620
12698
|
}
|
|
12621
12699
|
};
|
|
12622
12700
|
const jobParams = job.params;
|
|
12701
|
+
if ((jobType === "reference-annotation" || jobType === "generation") && Array.isArray(jobParams.entityTypes) && jobParams.entityTypes.length > 0) {
|
|
12702
|
+
const registered = await readEntityTypesProjection(project);
|
|
12703
|
+
const result = validateEntityTypes(registered, jobParams.entityTypes);
|
|
12704
|
+
if (!result.ok) {
|
|
12705
|
+
throw new Error(entityTypesNotRegisteredMessage(result.unknown));
|
|
12706
|
+
}
|
|
12707
|
+
}
|
|
12623
12708
|
if (jobType === "reference-annotation" && jobParams.entityTypes) {
|
|
12624
12709
|
jobParams.entityTypes = jobParams.entityTypes.map((et) => entityType(et));
|
|
12625
12710
|
}
|
|
12711
|
+
if (jobType === "tag-annotation") {
|
|
12712
|
+
const schemas = await readTagSchemasProjection(project);
|
|
12713
|
+
const result = resolveTagSchema(schemas, jobParams.schemaId);
|
|
12714
|
+
if (result.error !== void 0) {
|
|
12715
|
+
throw new Error(result.error);
|
|
12716
|
+
}
|
|
12717
|
+
jobParams.schema = result.schema;
|
|
12718
|
+
delete jobParams.schemaId;
|
|
12719
|
+
}
|
|
12626
12720
|
await jobQueue.createJob(job);
|
|
12627
12721
|
logger.info("Job created via bus", { jobId: job.metadata.id, jobType, correlationId });
|
|
12628
12722
|
eventBus.get("job:created").next({
|
|
@@ -12668,11 +12762,11 @@ function registerJobCommandHandlers(eventBus, jobQueue, parentLogger) {
|
|
|
12668
12762
|
}
|
|
12669
12763
|
|
|
12670
12764
|
// src/handlers/index.ts
|
|
12671
|
-
function registerBusHandlers(eventBus, knowledgeSystem, jobQueue, logger) {
|
|
12765
|
+
function registerBusHandlers(eventBus, knowledgeSystem, jobQueue, project, logger) {
|
|
12672
12766
|
registerAnnotationAssemblyHandler(eventBus, logger);
|
|
12673
12767
|
registerAnnotationLookupHandlers(eventBus, knowledgeSystem.kb, knowledgeSystem.gatherer, logger);
|
|
12674
12768
|
registerBindUpdateBodyHandler(eventBus, logger);
|
|
12675
|
-
registerJobCommandHandlers(eventBus, jobQueue, logger);
|
|
12769
|
+
registerJobCommandHandlers(eventBus, jobQueue, project, logger);
|
|
12676
12770
|
}
|
|
12677
12771
|
|
|
12678
12772
|
// src/service.ts
|
|
@@ -12783,7 +12877,7 @@ async function startMakeMeaning(project, config, eventBus, logger, options) {
|
|
|
12783
12877
|
const skipRebuild = options?.skipRebuild ?? process.env.SEMIONT_SKIP_REBUILD === "true";
|
|
12784
12878
|
const { jobQueue, jobStatusSubscription } = await createJobQueue(project, eventBus, logger);
|
|
12785
12879
|
const knowledgeSystem = await createKnowledgeSystemFromConfig(project, config, eventBus, logger, skipRebuild);
|
|
12786
|
-
registerBusHandlers(eventBus, knowledgeSystem, jobQueue, logger);
|
|
12880
|
+
registerBusHandlers(eventBus, knowledgeSystem, jobQueue, project, logger);
|
|
12787
12881
|
return {
|
|
12788
12882
|
knowledgeSystem,
|
|
12789
12883
|
jobQueue,
|