@aiaiai-pt/frankctl 0.7.2 → 0.8.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,109 @@
1
+ # Stage 11: all 237,808 regional schedule rows projected to the existing B3
2
+ # stop_time type. The source _record_id is generated once from the declared
3
+ # composite stream key (trip_id, Ordem, IdParagem), persisted in Bronze, and
4
+ # passed through unchanged here. Point-in-time profile: 237,808 non-null and
5
+ # distinct _record_id values, 8,964 trip keys, 2,411 stop keys, and zero
6
+ # relationship misses. These counts are evidence, never row limits.
7
+
8
+ apiVersion: frank.platform/v1
9
+ kind: IdentityPolicy
10
+ metadata:
11
+ name: cira-stop-time-external-id
12
+ spec:
13
+ name: cira-stop-time-external-id
14
+ strategy: passthrough
15
+ source_fields:
16
+ - _record_id
17
+ normalizers: []
18
+ emit_format: "cira:bdtransportes:stop_time:{resolved_key}"
19
+ collision_policy: error
20
+ ---
21
+ apiVersion: frank.platform/v1
22
+ kind: Pipeline
23
+ metadata:
24
+ name: cira_transport_stop_times
25
+ spec:
26
+ name: cira_transport_stop_times
27
+ description: Complete CIRA regional stop times with stable source identity and existing trip/stop links.
28
+ source_ids:
29
+ - cira-bdtransportes-horarios
30
+ schedule_config:
31
+ type: manual
32
+ steps:
33
+ - name: stop_time_rows
34
+ kind: field_mapping
35
+ params:
36
+ field_mappings:
37
+ - target_column: external_id
38
+ mapping_kind: identity
39
+ identity_policy:
40
+ name: cira-stop-time-external-id
41
+ field_order: 0
42
+ - source_column: schedule
43
+ target_column: arrival_time
44
+ mapping_kind: source_expression
45
+ field_order: 1
46
+ - source_column: schedule
47
+ target_column: departure_time
48
+ mapping_kind: source_expression
49
+ field_order: 2
50
+ - source_column: ordem
51
+ target_column: stop_sequence
52
+ mapping_kind: source_expression
53
+ custom_sql: "try_cast(${source} AS integer)"
54
+ field_order: 3
55
+ - source_column: trip_id
56
+ target_column: trip_id
57
+ mapping_kind: source_expression
58
+ field_order: 4
59
+ - source_column: id_paragem
60
+ target_column: stop_key
61
+ mapping_kind: source_expression
62
+ custom_sql: "concat('stop:',cast(try_cast(${source} AS bigint) AS varchar))"
63
+ field_order: 5
64
+ depends_on: []
65
+ emits_to: backing_dataset
66
+ config:
67
+ output_layer: silver
68
+ ---
69
+ apiVersion: frank.platform/v1
70
+ kind: BackingDataset
71
+ metadata:
72
+ name: cira_transport_stop_times
73
+ dependsOn:
74
+ - entityTypeId: transit_trip
75
+ ontologyTenantId: cira
76
+ - entityTypeId: transit_stop
77
+ ontologyTenantId: cira
78
+ spec:
79
+ entity_type_id: stop_time
80
+ entity_type_name: Stop Time
81
+ ontology_tenant_id: cira
82
+ pipeline: cira_transport_stop_times
83
+ sync_mode: manual
84
+ primary_key_column: external_id
85
+ title_key_column: external_id
86
+ property_mappings:
87
+ - column: external_id
88
+ property: external_id
89
+ type: varchar
90
+ is_primary_key: true
91
+ - column: arrival_time
92
+ property: arrival_time
93
+ type: varchar
94
+ - column: departure_time
95
+ property: departure_time
96
+ type: varchar
97
+ - column: stop_sequence
98
+ property: stop_sequence
99
+ type: integer
100
+ - column: trip_id
101
+ property: trip
102
+ is_relationship: true
103
+ target_type: transit_trip
104
+ target_key: service_id
105
+ - column: stop_key
106
+ property: stop
107
+ is_relationship: true
108
+ target_type: transit_stop
109
+ target_key: code
@@ -98,6 +98,16 @@ spec:
98
98
  - 580
99
99
  - 591
100
100
  - 592
101
+ regional-transport-stop-times:
102
+ title: Regional transport stop times with declarative identity
103
+ status: declared_pending
104
+ manifests:
105
+ - 11_stop_time.yaml
106
+ required_sources:
107
+ - cira-bdtransportes-horarios
108
+ issues:
109
+ - 592
110
+ - 650
101
111
  regional-transport-avl:
102
112
  title: Regional transport current vehicle positions
103
113
  status: live_proven
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/frankctl",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Terminal-first client for the Frank Low-Code Pipeline platform",
5
5
  "homepage": "https://franksdocs.nomadriver.co",
6
6
  "repository": {
@@ -85,12 +85,13 @@ an explicit subset. Repeat `--set frank_tenant_prefix=<8-hex>` and
85
85
  ontology tenant. The CIRA pack treats ontology schema as externally owned and therefore
86
86
  contains no `ensure_schema` mutation.
87
87
 
88
- A pipeline is one multi-document YAML file describing three kinds of object under the
88
+ A pipeline is one multi-document YAML file describing four kinds of object under the
89
89
  `frank.platform/v1` API version:
90
90
 
91
91
  | `kind` | Role |
92
92
  |--------|------|
93
93
  | `Source` | Extract/Load config — connector `pattern_id`, streams, schedule → Iceberg **bronze** |
94
+ | `IdentityPolicy` | Stable-key recipe reconciled and pinned before a consuming transform |
94
95
  | `Pipeline` | Transform steps (SQL/Python) turning bronze → **silver/gold** |
95
96
  | `BackingDataset` | Maps a silver table's columns to **ontology** entity properties |
96
97
 
@@ -139,8 +140,8 @@ separate target entity readback.
139
140
 
140
141
  | Flag | What it does |
141
142
  |------|--------------|
142
- | `--dry-run` | Client-side parse + cross-ref resolve. Catches malformed YAML and broken references. No server call. |
143
- | `--dry-run-server` | Server-side preflight against the catalog + DB without persisting. Catches unknown `pattern_id`s, would-be immutable-diff conflicts, and unresolved refs. |
143
+ | `--dry-run` | Client-side parse + local cross-ref validation. Catches malformed YAML and broken same-file references. No server call. |
144
+ | `--dry-run-server` | Server-side preflight against the catalog + DB without persisting. Catches unknown `pattern_id`s, identity-policy create/noop/patch/new-version state, would-be immutable-diff conflicts, and unresolved refs. |
144
145
  | `--dry-run-server --live-ontology` | Adds an opt-in, GET-only check of ontology tenant, type, properties, relationships, keys, and declared carrier types. It never changes ontology schema or entities. |
145
146
 
146
147
  Run `--dry-run-server` in CI on every change; run `apply --wait` to promote.
@@ -157,6 +158,60 @@ BackingDataset and set `spec.sync_mode: manual`. `--wait` topologically orders s
157
158
  dependencies and requires external dependencies in the same ontology tenant to be
158
159
  already `synced`. Cycles and self-referential relationships fail before execution.
159
160
 
161
+ ### Stable identity in declarative pipelines
162
+
163
+ Declare a tenant-owned recipe once, then reference it by name from an identity field
164
+ mapping. `frankctl` applies in `Source → IdentityPolicy → Pipeline → BackingDataset`
165
+ order and replaces the symbolic reference with the exact returned policy ID/version;
166
+ UUIDs do not belong in tracked YAML.
167
+
168
+ ```yaml
169
+ apiVersion: frank.platform/v1
170
+ kind: IdentityPolicy
171
+ metadata: { name: source-record-id }
172
+ spec:
173
+ name: source-record-id
174
+ strategy: passthrough
175
+ source_fields: [_record_id]
176
+ normalizers: []
177
+ emit_format: "my-provider:my-type:{resolved_key}"
178
+ collision_policy: error
179
+ ---
180
+ apiVersion: frank.platform/v1
181
+ kind: Pipeline
182
+ metadata: { name: my-pipeline }
183
+ spec:
184
+ source_ids: [my-source]
185
+ steps:
186
+ - name: project
187
+ kind: field_mapping
188
+ params:
189
+ field_mappings:
190
+ - target_column: external_id
191
+ mapping_kind: identity
192
+ identity_policy: { name: source-record-id }
193
+ field_order: 0
194
+ ```
195
+
196
+ Same-file references contain only `name`; external policies require exact `{name, tier,
197
+ version}`. Never author raw UUID/version/hash pins: `frankctl` resolves symbolic YAML
198
+ and carries the server-returned recipe hash as an internal optimistic precondition.
199
+
200
+ Policy reconciliation reports `create`, `noop`, `patch`, or `new-version`. First use freezes
201
+ a recipe globally; later changes version it. Reads compute tenant-live `used_in`, including
202
+ for system policies, without overwriting a shared system cache. Multi-document apply is
203
+ sequential, not atomic: correct a failure and reapply the complete declaration.
204
+
205
+ Pipeline activation locks Pipeline, affected Transforms in deterministic UUID order with
206
+ refreshed mapping/source snapshots, then old+new policies in UUID/version order. Direct
207
+ Transform PUT/DELETE locks Transform, then sorted policies. This prevents deadlock and
208
+ stale `used_in`. A changed recipe still returns `409 identity_policy_recipe_changed`;
209
+ reapply the complete file. Dry-run observes state but does not reserve it.
210
+
211
+ Migration `s43_identity_policy_frozen` scans only historical `mapping_kind: identity` pins.
212
+ It recognizes canonical/uppercase/hyphenless/braced/URN UUIDs and normalized positive
213
+ versions without casting malformed JSON. Author symbolically; never mutate ontology schema.
214
+
160
215
  ### Immutable state & when to force
161
216
 
162
217
  Backing Datasets carry operational state (ontology cursor, sync history). If a change
@@ -179,8 +234,10 @@ to a file for review:
179
234
  frankctl pipelines export <pipeline-id> > pipeline.yaml
180
235
  ```
181
236
 
182
- The export includes ordinary Source configuration, opaque credential references, and
183
- config-only stream declarations, but omits credential values and stream runtime state.
237
+ The export includes ordinary Source configuration, opaque credential references,
238
+ config-only stream declarations, complete Pipeline steps, and portable symbolic
239
+ IdentityPolicy references. It omits credential values, stream runtime state, and
240
+ tenant-local policy UUIDs.
184
241
 
185
242
  ### Minimal manifest shape
186
243
 
@@ -429,6 +486,9 @@ you can review, edit, and feed straight into `pipelines apply`.
429
486
  - **401 after login**: your token expired and refresh failed — `frankctl auth login` again.
430
487
  - **`409 immutable_diff` on apply**: a Backing Dataset change would destroy state; see
431
488
  `--allow-recreate` above and decide deliberately.
489
+ - **`409 identity_policy_recipe_changed` on apply**: replay the complete manifest so
490
+ policy reconciliation returns a fresh pin; do not add runtime pins to YAML or retry
491
+ only the Pipeline request.
432
492
  - **`apply` did nothing**: that's idempotency — the declared state already matches. Use
433
493
  `-v` to see the reconcile plan, or `--dry-run-server` to preview.
434
494