Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions crates/tepp_api/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ pub struct JsonLdExport {
pub contract_version: u16,
/// `JSON-LD` context URI or term map serialized as string for v1.
pub context: String,
/// Primary node identifier.
pub id: String,
/// Primary node identifier. The v1 wire key remains `id` for compatibility.
#[serde(rename = "id")]
pub node_id: String,
/// Node type label.
pub type_name: String,
/// Opaque payload digest the consumer must verify separately.
Expand Down Expand Up @@ -120,14 +121,14 @@ impl JsonLdExport {
/// Returns field-validation errors for empty values.
pub fn new(
context: impl Into<String>,
id: impl Into<String>,
node_id: impl Into<String>,
type_name: impl Into<String>,
artifact_digest_sha256: impl Into<String>,
) -> Result<Self, ApiError> {
let export = Self {
contract_version: EXPORT_CONTRACT_VERSION,
context: context.into(),
id: id.into(),
node_id: node_id.into(),
type_name: type_name.into(),
artifact_digest_sha256: artifact_digest_sha256.into(),
};
Expand Down Expand Up @@ -159,7 +160,7 @@ impl JsonLdExport {
fn validate(&self) -> Result<(), ApiError> {
require_contract_version(self.contract_version, EXPORT_CONTRACT_VERSION)?;
require_nonempty(&self.context)?;
require_nonempty(&self.id)?;
require_nonempty(&self.node_id)?;
require_nonempty(&self.type_name)?;
require_nonempty(&self.artifact_digest_sha256)?;
Ok(())
Expand Down
23 changes: 23 additions & 0 deletions crates/tepp_api/tests/jsonld_semantic_identifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! Contract regression for semantic Rust identifiers on the JSON-LD export seam.

use tepp_api::JsonLdExport;

#[test]
fn jsonld_export_uses_node_id_in_rust_and_preserves_v1_wire_id() {
let export = JsonLdExport::new(
"https://example.org/tepp/context.jsonld",
"urn:tepp:artifact:1",
"ValidationReport",
"abc123",
)
.expect("valid JSON-LD export");

assert_eq!(export.node_id, "urn:tepp:artifact:1");

let wire_json = export.to_json().expect("serialize JSON-LD export");
assert!(wire_json.contains(r#""id":"urn:tepp:artifact:1""#));
assert!(!wire_json.contains("node_id"));

let decoded = JsonLdExport::from_json(&wire_json).expect("deserialize v1 JSON-LD export");
assert_eq!(decoded.node_id, export.node_id);
}
4 changes: 4 additions & 0 deletions docs/connectors/naruon-artifact-consumer.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ TEPP remains the scientific authority for estimation, recovery metrics, temporal

Committed examples live under `examples/`. Schemas for analysis-run requests and corpus-split manifests live under `schemas/`.

For the `JsonLdExport` v1 boundary, Rust source code uses the semantic field name `node_id`; Serde deliberately preserves the existing serialized key `id`. Consumers must treat the versioned wire contract—not an internal Rust field spelling—as the interoperability boundary. A future change from the v1 `id` term to another JSON-LD representation such as `@id` requires an explicit versioned contract decision rather than an implicit source rename.

## Purpose-bound disclosure

When naruon requests an export, TEPP evaluates `AnalyticalPurpose::ModularServiceConsumer`. Free-text source bodies remain optional and purpose-gated; opaque analytical identifiers and membership/relation structure must not be blanket-masked when required for multilevel measurement (ADR 0009).
Expand Down Expand Up @@ -59,3 +61,5 @@ Klyne, G., & Newman, C. (2002). *Date and time on the Internet: Timestamps* (RFC
ISO/IEC. (2019). *ISO/IEC 27701:2019 Security techniques — Extension to ISO/IEC 27001 and ISO/IEC 27002 for privacy information management — Requirements and guidelines*. International Organization for Standardization.

National Institute of Standards and Technology. (2020). *NIST Privacy Framework: A tool for improving privacy through enterprise risk management* (Version 1.0). U.S. Department of Commerce. https://doi.org/10.6028/NIST.CSWP.01162020

World Wide Web Consortium. (2020, July 16). *JSON-LD 1.1: A JSON-based serialization for linked data* (W3C Recommendation). https://www.w3.org/TR/json-ld11/
39 changes: 39 additions & 0 deletions docs/doctoring/jsonld-node-semantic-identifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# JSON-LD node semantic identifier

## Decision

The `tepp_api::JsonLdExport` Rust source contract names the primary node identity `node_id` rather than the underspecified bare field `id`. The existing version-1 JSON wire remains byte-key compatible at the field-name level: Serde maps the Rust `node_id` field to the serialized/deserialized key `"id"`.

This is a source-contract naming repair, not a v1 wire-format migration. Organization search found no ContextualWisdomLab source consumer accessing `JsonLdExport.id`; the published consumer boundary is the versioned serialized artifact. The crate is also marked `publish = false`, so the repository remains the source owner for this API surface.

## DDD boundary

**Bounded context:** Analytical Artifact Export.

**Aggregate:** `JsonLdExport` is a versioned export envelope owned by `tepp_api`.

**Value object:** `node_id` identifies the primary node represented by the envelope. `type_name` describes the node type and `artifact_digest_sha256` binds the envelope to the artifact payload.

**Invariant:** internal Rust names must identify the domain role without relying on a generic `id`; the existing v1 serialized key remains stable so naruon and other modular consumers do not require a coordinated wire migration.

## JSON-LD compatibility

JSON-LD 1.1 defines node identifiers through the `@id` keyword and permits terms to be defined through a context. TEPP v1 already has a deployed envelope field named `id`; this change does not claim that bare `id` is itself the normative JSON-LD keyword and does not silently rewrite the wire to `@id`. The existing contract is preserved until a separately versioned wire decision is made.

The new regression test proves both sides of the anti-corruption seam: Rust code reads `node_id`, while serialized v1 JSON contains `"id"` and not `"node_id"`; deserializing the same v1 payload restores `node_id`.

## Security, privacy, and scientific scope

No estimator, psychometric arithmetic, temporal semantics, evidence cutoff, PII authority, database schema, network endpoint, or export authorization rule changes. The node identifier remains an opaque string and passes the same non-empty validation. `deny_unknown_fields` remains enabled.

## Verification

The RED commit introduced an external crate test that accessed `JsonLdExport.node_id` while production still exposed only `id`, producing a compile-time contract failure. The production repair then renamed the Rust field and constructor parameter and added `#[serde(rename = "id")]` to preserve the wire contract. Fresh exact-head repository and central checks remain the merge authority.

## References

Feitelson, D. G., Mizrahi, A., Noy, N., Ben Shabat, A., Eliyahu, O., & Sheffer, R. (2022). How developers choose names. *IEEE Transactions on Software Engineering, 48*(1), 37–52. https://doi.org/10.1109/TSE.2020.2976920

Schankin, A., Berger, A., Holt, D. V., Hofmeister, J. C., Riedel, T., & Beigl, M. (2018). Descriptive compound identifier names improve source code comprehension. In *Proceedings of the 26th Conference on Program Comprehension* (pp. 31–40). Association for Computing Machinery. https://doi.org/10.1145/3196321.3196332

World Wide Web Consortium. (2020, July 16). *JSON-LD 1.1: A JSON-based serialization for linked data* (W3C Recommendation). https://www.w3.org/TR/json-ld11/
Loading