Skip to content

feat(ports): WoaPort + SmbPort — planner-times align with billable hours (cross-fork convergence pin)#93

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/woa-smb-portspecs
Jun 22, 2026
Merged

feat(ports): WoaPort + SmbPort — planner-times align with billable hours (cross-fork convergence pin)#93
AdaWorldAPI merged 1 commit into
mainfrom
claude/woa-smb-portspecs

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

What

Two new PortSpec impls in ogar-vocab::ports:

  • WoaPort (NAMESPACE=WorkOrder, BRIDGE_ID=woa, 25 aliases)
  • SmbPort (NAMESPACE=SMB, BRIDGE_ID=smb, 20 aliases)

Both map their public names (German + English synonyms collapse to the same canonical id) onto the existing commerce 0x02XX block and the 0x01XX BILLABLE_WORK_ENTRY constant — no new class_ids minted, just new ports consuming the existing codebook.

The operator value statement, as a test

From the operator 2026-06-21: "in the end planning (openproject) and ERP (odoo, YOU) should become reusable ontologies so that the planner times can align with billable hours."

That's the cross-fork convergence the codebook was built for, applied to the planner ↔ ERP seam. This PR ships it as data:

// time_entry_converges_across_planner_and_erp_ports
OpenProjectPort::class_id("TimeEntry")        == Some(0x0103)
RedminePort::class_id("TimeEntry")            == Some(0x0103)
WoaPort::class_id("Stundenzettel")            == Some(0x0103)
WoaPort::class_id("TimesheetActivity")        == Some(0x0103)
WoaPort::class_id("TimeEntry")                == Some(0x0103)
WoaPort::class_id("Zeiterfassung")            == Some(0x0103)
SmbPort::class_id("Stundenzettel")            == Some(0x0103)
SmbPort::class_id("TimeEntry")                == Some(0x0103)
SmbPort::class_id("Zeiterfassung")            == Some(0x0103)

All resolve to class_ids::BILLABLE_WORK_ENTRY = 0x0103. The planner→ERP integration becomes a codebook lookup, not a translation layer — exactly the value the operator's statement calls for.

The commerce-block convergence (apple meets apple, second time)

Symmetric to the existing OpenProject ↔ Redmine pin (openproject_and_redmine_converge_on_shared_concepts), WoA ↔ SMB now have one too:

// woa_and_smb_converge_on_commerce_block (16 concept pairs)
WoaPort::class_id("Customer") == SmbPort::class_id("Customer") == Some(BILLING_PARTY)
WoaPort::class_id("Kunde")    == SmbPort::class_id("Kunde")    == Some(BILLING_PARTY)
WoaPort::class_id("Rechnung") == SmbPort::class_id("Rechnung") == Some(COMMERCIAL_DOCUMENT)
// … 13 more

Alias tables

WoA (25): 2 BillingParty (Customer/Kunde) + 11 CommercialDocument (Vorgang umbrella + Quote/Angebot, Order/Auftrag, Invoice/Rechnung, CreditNote/Gutschrift, RecurringInvoice) + 2 LineItem + 3 TaxPolicy + 3 PaymentRecord + 4 BillableWorkEntry.

SMB (20): 2 BillingParty + 8 CommercialDocument (Auftrag/Order, Rechnung/Invoice, Angebot/Quote, Gutschrift/CreditNote) + 2 LineItem + 3 TaxPolicy + 2 PaymentRecord + 3 BillableWorkEntry. SMB omits WoA-only synonyms (Vorgang umbrella, RecurringInvoice, TimesheetActivity).

Intentionally absent today (negative pins): SmbPort::class_id("Artikel") and ::class_id("Product") return None — SKU / Product / Article aren't in the codebook yet; that's a follow-up extension of the 0x02XX block.

Tests

cargo test -p ogar-vocab ports::
running 19 tests
… all 19 passed in 0.00s

New tests:

  • woa_namespace_and_bridge_id_match_canonical_strings
  • smb_namespace_and_bridge_id_match_canonical_strings
  • time_entry_converges_across_planner_and_erp_portsthe value-statement pin
  • woa_and_smb_converge_on_commerce_block (16 concept pairs)
  • woa_and_smb_unknown_public_names_resolve_to_none (incl. Artikel/Product negative pins)
  • each_alias_class_id_is_in_the_codebook (extended to WoaPort + SmbPort)
  • each_port_has_expected_alias_count (extended with WoA=25, SMB=20 assertions)

cargo fmt --check clean on ports.rs. Pre-existing clippy warnings in lib.rs (~10 derivable-impl + empty-line-after-attribute) are unchanged by this PR; my additions to ports.rs are clippy-clean.

Consumer impact (queued, separate sessions)

After this lands:

  • lance-graph: add lance_graph_ogar::bridges::WoaBridge = UnifiedBridge<WoaPort> and SmbBridge = UnifiedBridge<SmbPort> type aliases (~5 LOC each, sibling of MedcareBridge / OpenProjectBridge / RedmineBridge).
  • woa-rs: switch use lance_graph_ontology::bridges::WoaBridgeuse lance_graph_ogar::bridges::WoaBridge across src/registry.rs, src/unified_bridge.rs, src/lib.rs, tests/ontology_cypher_round_trip.rs. ~50 LOC. Iron Rule 1 in woa-rs/CLAUDE.md needs an allow-list update (RFC) for lance-graph-ogar. See woa-rs/.claude/board/OGAR-MIGRATION-GAP-2026-06-21.md.
  • smb-office-rs: switch OgitBridgeSmbBridge in crates/smb-bridge/src/unified_bridge_wiring.rs. ~50 LOC. See smb-office-rs/.claude/board/TECH_DEBT.md TD-OGAR-CONSUMER-MIGRATION-1.

Cross-refs

  • lance-graph PR #585 (the OGIT/OGAR separation that left WoA + SMB consumers behind)
  • lance-graph EPIPHANIES E-OGAR-AR-MIGRATION-IS-SEVERITY
  • lance-graph ISSUES OGAR-AR-MIGRATION-WOA-SMB-OPEN
  • woa-rs .claude/board/OGAR-MIGRATION-GAP-2026-06-21.md
  • smb-office-rs .claude/board/TECH_DEBT.md TD-OGAR-CONSUMER-MIGRATION-1
  • smb-office-rs .claude/board/EPIPHANIES.md E-PLANNER-TIMES-ALIGN-BILLABLE-HOURS

🤖 Generated with Claude Code

https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx

…e hours

Operator value statement 2026-06-21: "in the end planning (openproject)
and ERP (odoo, YOU) should become reusable ontologies so that the planner
times can align with billable hours." That's the cross-fork convergence
the codebook was built for, applied to the planner ↔ ERP seam.

This PR mints two PortSpec impls that make the convergence work by data:

- WoaPort (NAMESPACE="WorkOrder", BRIDGE_ID="woa", 25 aliases):
  - Customer/Kunde → BILLING_PARTY (0x0204)
  - Vorgang/WorkOrder/Quote/Angebot/Order/Auftrag/Invoice/Rechnung/
    CreditNote/Gutschrift/RecurringInvoice → COMMERCIAL_DOCUMENT (0x0202)
  - Position/LineItem → COMMERCIAL_LINE_ITEM (0x0201)
  - TaxRate/Steuersatz/Tax → TAX_POLICY (0x0203)
  - Payment/Zahlung/PaymentRecord → PAYMENT_RECORD (0x0205)
  - Stundenzettel/TimesheetActivity/TimeEntry/Zeiterfassung →
    BILLABLE_WORK_ENTRY (0x0103)  ← the planner-ERP convergence pin
- SmbPort (NAMESPACE="SMB", BRIDGE_ID="smb", 20 aliases):
  - Kunde/Customer → BILLING_PARTY
  - Auftrag/Order/Rechnung/Invoice/Angebot/Quote/Gutschrift/CreditNote →
    COMMERCIAL_DOCUMENT
  - Position/LineItem → COMMERCIAL_LINE_ITEM
  - Steuer/Tax/Steuersatz → TAX_POLICY
  - Zahlung/Payment → PAYMENT_RECORD
  - Stundenzettel/TimeEntry/Zeiterfassung → BILLABLE_WORK_ENTRY

Both ports include German and English synonyms collapsing to the same
canonical id — consumers can use either vocabulary and route to the
same dispatch arm.

The convergence pin (the headline test):
- `time_entry_converges_across_planner_and_erp_ports` asserts that
  OpenProjectPort::TimeEntry == RedminePort::TimeEntry == WoaPort::
  Stundenzettel == WoaPort::TimesheetActivity == WoaPort::TimeEntry ==
  WoaPort::Zeiterfassung == SmbPort::Stundenzettel == SmbPort::
  TimeEntry == SmbPort::Zeiterfassung — all 0x0103 BILLABLE_WORK_ENTRY.
  Drift here re-introduces the manual translation layer the codebook
  exists to eliminate.

Plus the commerce-block convergence pin:
- `woa_and_smb_converge_on_commerce_block` asserts the symmetric
  pattern OpenProject ↔ Redmine has: WoA Customer ↔ SMB Kunde →
  BILLING_PARTY; WoA Rechnung ↔ SMB Rechnung → COMMERCIAL_DOCUMENT;
  etc. across 16 concept pairs spanning the full commerce block.

Plus the smaller wires: namespace/bridge_id pins, unknown-name-resolves-
to-None tests (incl. negative pins for SmbPort::Artikel and ::Product —
these are intentionally absent today, will need a codebook extension
when SMB needs them).

Consumer impact (out of this repo's scope, queued):
- lance-graph: `lance_graph_ogar::bridges::WoaBridge` =
  `UnifiedBridge<WoaPort>` and `lance_graph_ogar::bridges::SmbBridge`
  = `UnifiedBridge<SmbPort>` type aliases (~5 LOC each, sibling of
  MedcareBridge / OpenProjectBridge / RedmineBridge).
- woa-rs: switch `use lance_graph_ontology::bridges::WoaBridge` to
  `use lance_graph_ogar::bridges::WoaBridge` across 4 files (~50 LOC).
  See woa-rs `.claude/board/OGAR-MIGRATION-GAP-2026-06-21.md`.
- smb-office-rs: switch `use lance_graph_ontology::bridges::OgitBridge`
  to `use lance_graph_ogar::bridges::SmbBridge` in
  `crates/smb-bridge/src/unified_bridge_wiring.rs` (~50 LOC). See
  smb-office-rs `.claude/board/TECH_DEBT.md` TD-OGAR-CONSUMER-
  MIGRATION-1.

Tests: 19/19 ports tests pass (incl. the new 6 tests — woa NS/ID,
smb NS/ID, planner-ERP convergence, commerce convergence, unknowns→
None, alias-counts).

Pre-existing clippy errors in `lib.rs` (~10 derivable-impl + empty-
line-after-attribute warnings) are unrelated to this PR; my additions
to `ports.rs` are clippy-clean.

Cross-refs: lance-graph PR #585 (the OGIT/OGAR separation that left
the WoA + SMB consumers behind); lance-graph EPIPHANIES
E-OGAR-AR-MIGRATION-IS-SEVERITY; lance-graph ISSUES
OGAR-AR-MIGRATION-WOA-SMB-OPEN.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xzyc27Nx3f8WC5KzwfWfjx
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@AdaWorldAPI AdaWorldAPI merged commit 1a75492 into main Jun 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant