@larc-iu/plaid-client 0.1.0-alpha.9 → 0.2.0-alpha.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,123 @@
1
+ /**
2
+ * Provenance: the cross-app convention for distinguishing machine-provided
3
+ * from human-labeled information, expressed as flat metadata keys on
4
+ * annotation entities (spans, relations, vocab links, optionally tokens).
5
+ * Flat scalar keys — the query engine matches flat metadata well, nested
6
+ * objects poorly.
7
+ *
8
+ * Three states (the PRESENCE of the `prov` key, not its value, is the
9
+ * discriminator; absent keys mean "human"):
10
+ *
11
+ * human — no prov keys; a person made it.
12
+ * machine — { prov: 'inferred', provSource: '<producer>' };
13
+ * an algorithm/service made it, no human has vouched.
14
+ * verified — same + { provConfirmed: true }; machine-made and a
15
+ * human confirmed (or edited) it. provSource stays so
16
+ * machine origin remains traceable.
17
+ *
18
+ * The write contract every machine writer must follow:
19
+ * 1. Machine writers may freely replace MACHINE (unverified) material.
20
+ * 2. Machine writers must never modify or delete human or verified
21
+ * material unless explicitly told to overwrite (an explicit, per-run,
22
+ * user-facing opt-in — for services, a declared boolean `overwrite`
23
+ * parameter).
24
+ * 3. Any human edit of a machine annotation verifies it: the edit also
25
+ * stamps { provConfirmed: true } (see verifyOnEdit).
26
+ *
27
+ * Producer naming: 'service:<serviceId>' for services (use serviceSource),
28
+ * 'rule:<name>' for built-in rule algorithms, app-specific ids like
29
+ * 'gloss:doc-frequency' or 'flex-import' otherwise.
30
+ *
31
+ * PREDICTION EXTRAS. A producer may also record how confident it was and
32
+ * what else it considered, in two reserved slots split along the
33
+ * queryability line:
34
+ * - provProb: ONE flat number in [0, 1] — the producer's probability for
35
+ * the value it chose. Flat scalars are what the query engine filters
36
+ * and orders on, so "review the least-confident machine output first"
37
+ * is an ordinary query. Omit it unless you can honestly produce a
38
+ * probability (a raw logprob is NOT one — put it in provDetail).
39
+ * - provDetail: ONE open map for everything else — top-k alternatives or
40
+ * distributions, model name/version, raw scores. Deliberately nested
41
+ * (not queryable); keep it small (top-k, not whole-vocabulary dumps).
42
+ * Both describe the machine's ORIGINAL prediction. They are kept after
43
+ * human edits (history is valuable), so a consumer must not present
44
+ * provProb as confidence in the CURRENT value once the entity is verified
45
+ * (provConfirmed is exactly the flag to check).
46
+ */
47
+
48
+ /** The flat metadata keys of the provenance convention. */
49
+ export const PROV = Object.freeze({
50
+ /** 'inferred' when produced by an algorithm/service; absent = human. */
51
+ key: 'prov',
52
+ /** Which producer, e.g. 'service:<id>', 'rule:precedent-or-unique'. */
53
+ sourceKey: 'provSource',
54
+ /** true once a human confirmed (or edited) the inferred value. */
55
+ confirmedKey: 'provConfirmed',
56
+ /** Producer's probability ([0,1]) for the chosen value. Flat = queryable. */
57
+ probKey: 'provProb',
58
+ /** Open map of producer extras (alternatives, model version, raw scores). */
59
+ detailKey: 'provDetail',
60
+ /** The (currently only) value of `prov`. Presence, not value, decides. */
61
+ INFERRED: 'inferred',
62
+ });
63
+
64
+ /** The three provenance states returned by provState. */
65
+ export const PROV_STATES = Object.freeze({
66
+ HUMAN: 'human',
67
+ MACHINE: 'machine',
68
+ VERIFIED: 'verified',
69
+ });
70
+
71
+ /**
72
+ * The metadata fragment a machine writer merges into everything it creates.
73
+ * @param {string} source - producer id, e.g. serviceSource(serviceId)
74
+ * @param {Object} [extras] - optional prediction extras:
75
+ * @param {number} [extras.prob] - probability in [0,1] for the chosen value
76
+ * (omit unless it honestly is one — raw scores go in detail)
77
+ * @param {Object} [extras.detail] - open map of producer extras (top-k
78
+ * alternatives, model version, raw scores; keep it small)
79
+ */
80
+ export const stampInferred = (source, { prob, detail } = {}) => ({
81
+ [PROV.key]: PROV.INFERRED,
82
+ [PROV.sourceKey]: source,
83
+ ...(prob !== undefined && prob !== null ? { [PROV.probKey]: prob } : {}),
84
+ ...(detail ? { [PROV.detailKey]: detail } : {}),
85
+ });
86
+
87
+ /**
88
+ * The fragment for machine-made material that is born verified (e.g. an
89
+ * import carrying upstream human approval, or a guess written only on
90
+ * explicit user confirmation). Takes the same optional prediction extras
91
+ * as stampInferred.
92
+ */
93
+ export const confirmedInferred = (source, extras = {}) => ({
94
+ ...stampInferred(source, extras),
95
+ [PROV.confirmedKey]: true,
96
+ });
97
+
98
+ /**
99
+ * Classify an entity's metadata into one of PROV_STATES.
100
+ * @param {Object|null|undefined} metadata
101
+ * @returns {'human'|'machine'|'verified'}
102
+ */
103
+ export const provState = (metadata) => {
104
+ if (!metadata || metadata[PROV.key] == null) return PROV_STATES.HUMAN;
105
+ return metadata[PROV.confirmedKey] ? PROV_STATES.VERIFIED : PROV_STATES.MACHINE;
106
+ };
107
+
108
+ /**
109
+ * Whether a machine writer must leave this entity alone (write-contract
110
+ * rule 2): true for human-made and human-verified material.
111
+ */
112
+ export const isProtected = (metadata) => provState(metadata) !== PROV_STATES.MACHINE;
113
+
114
+ /**
115
+ * The metadata fragment a HUMAN edit of this entity should merge in
116
+ * (write-contract rule 3): { provConfirmed: true } when the entity is
117
+ * machine-made and unverified, else null (nothing to do).
118
+ */
119
+ export const verifyOnEdit = (metadata) =>
120
+ (provState(metadata) === PROV_STATES.MACHINE ? { [PROV.confirmedKey]: true } : null);
121
+
122
+ /** Canonical provSource for a service: 'service:<serviceId>'. */
123
+ export const serviceSource = (serviceId) => `service:${serviceId}`;
@@ -33,14 +33,20 @@
33
33
  * snake_case key round-trips unchanged to a JS UI and back.
34
34
  */
35
35
 
36
- /** The controlled task vocabulary — the fixed integration-point goals. */
36
+ /**
37
+ * The controlled task vocabulary — the fixed integration-point goals.
38
+ *
39
+ * Whatever the task, a service should stamp everything it creates with the
40
+ * provenance convention (see ./provenance.js: stampInferred(serviceSource(id)))
41
+ * so UIs can distinguish machine output until a human verifies it, and should
42
+ * honor the write contract: never destroy human-made or verified material
43
+ * without an explicit `overwrite` parameter. See the manual, "Provenance".
44
+ */
37
45
  export const TASKS = Object.freeze({
38
46
  TOKENIZE: 'tokenize',
39
47
  PARSE: 'parse',
40
48
  TRANSCRIBE: 'transcribe',
41
- /** Create vocab links for unlinked tokens. Services should stamp link
42
- * metadata with the provenance convention ({ prov: 'inferred', provSource:
43
- * 'service:<id>' }) so UIs can distinguish machine links until confirmed. */
49
+ /** Create vocab links for unlinked tokens. */
44
50
  LINK_VOCAB: 'link-vocab',
45
51
  });
46
52
 
package/src/services.js CHANGED
@@ -11,18 +11,34 @@
11
11
  import { transformRequest, transformResponse } from './transforms.js';
12
12
 
13
13
  /**
14
- * Discover the services currently connected to a project — a synchronous GET.
15
- * `timeout` is accepted for back-compat and ignored.
14
+ * Discover the services seen on a project — a synchronous GET. Returns every
15
+ * service ever registered on the project: currently connected ones carry
16
+ * `online: true`; previously-seen offline ones carry `online: false` plus a
17
+ * `lastSeenAt` stamp. Callers that need a service they can actually submit
18
+ * work to should filter on `online`.
16
19
  *
17
20
  * @param {Object} client - PlaidClient instance
18
21
  * @param {string} projectId - Project UUID
19
- * @param {number} [timeout] - Ignored
20
- * @returns {Promise<Array>} [{serviceId, serviceName, description, extras}]
22
+ * @returns {Promise<Array>} [{serviceId, serviceName, description, extras, online, lastSeenAt}]
21
23
  */
22
- export function discoverServices(client, projectId, timeout) {
24
+ export function discoverServices(client, projectId) {
23
25
  return client._request('GET', `/api/v1/projects/${projectId}/services`);
24
26
  }
25
27
 
28
+ /**
29
+ * Forget a previously-seen (offline) service: removes its row from the
30
+ * project's persistent registry. Maintainer-only; 409 if the service is
31
+ * currently connected (it would just re-register).
32
+ *
33
+ * @param {Object} client - PlaidClient instance
34
+ * @param {string} projectId - Project UUID
35
+ * @param {string} serviceId - Service ID to forget
36
+ * @returns {Promise<void>}
37
+ */
38
+ export function discardService(client, projectId, serviceId) {
39
+ return client._request('DELETE', `/api/v1/projects/${projectId}/services/${encodeURIComponent(serviceId)}`);
40
+ }
41
+
26
42
  /**
27
43
  * Register a service and handle incoming work requests.
28
44
  *
package/src/sse.js CHANGED
@@ -75,6 +75,13 @@ export function createSSEConnection(client, projectId, onEvent, path) {
75
75
  });
76
76
 
77
77
  if (!response.ok) {
78
+ if (response.status === 409 && streamPath.includes('/services/')) {
79
+ // Service channel rejected: another live instance holds this
80
+ // service-id. The serve() reconnect timer will retry — once the
81
+ // other instance is gone (or its dead channel is reaped) the retry
82
+ // takes over.
83
+ console.warn('Service registration rejected (409): another instance of this service is already connected; will retry');
84
+ }
78
85
  throw new Error(`HTTP ${response.status} ${response.statusText}`);
79
86
  }
80
87