refactor(host_api): centralize system-sentinel id minting and pin authority contract - #4584
matiasbenary wants to merge 7 commits into
Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR standardizes construction of the “system” sentinel IDs across crates and updates serde behavior so ResourceScope::system() can round-trip through JSON while keeping the sentinel value unconstructible via normal new() validators.
Changes:
- Introduces
system_sentinel()as the blessed constructor for sentinel-valued IDs and updates call sites to use it. - Updates the
string_id!macro to generate strict vs. sentinel-awareDeserializeimplementations (TenantId/UserId only). - Adds host API contract tests pinning JSON round-trip behavior and ensuring non-opted-in ID types reject the sentinel.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/ironclaw_turns/src/scope.rs | Switches fallback user_id construction to UserId::system_sentinel() |
| crates/ironclaw_threads/src/contract.rs | Switches fallback user_id construction to UserId::system_sentinel() |
| crates/ironclaw_loop_support/src/budget_accountant.rs | Switches fallback user_id construction to UserId::system_sentinel() and drops direct constant usage |
| crates/ironclaw_host_api/src/resource.rs | Routes ResourceScope::system() through the new sentinel constructors |
| crates/ironclaw_host_api/src/ids.rs | Extends string_id! to support strict vs sentinel-aware deserialization and adds system_sentinel() |
| crates/ironclaw_host_api/tests/host_api_contract.rs | Adds tests pinning sentinel behavior across serialization/deserialization and ID kinds |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| impl<'de> Deserialize<'de> for $name { | ||
| fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
| where | ||
| D: serde::Deserializer<'de>, | ||
| { | ||
| let value = String::deserialize(deserializer)?; | ||
| // Admit the system sentinel; `Self::new` rejects its | ||
| // control bytes, so JSON round-trip needs an explicit bypass. | ||
| if value == crate::SYSTEM_RESERVED_ID { | ||
| return Ok(Self::system_sentinel()); | ||
| } | ||
| Self::new(value).map_err(serde::de::Error::custom) | ||
| } |
| /// validation rejects, so no user-supplied identifier can collide. | ||
| pub fn system() -> Self { | ||
| Self { | ||
| tenant_id: TenantId::from_trusted(SYSTEM_RESERVED_ID.to_string()), | ||
| user_id: UserId::from_trusted(SYSTEM_RESERVED_ID.to_string()), | ||
| tenant_id: TenantId::system_sentinel(), | ||
| user_id: UserId::system_sentinel(), |
| // Sentinel stays unreachable through ordinary construction; only | ||
| // `from_trusted` may produce it, so user input can never collide. | ||
| assert!(TenantId::new(SYSTEM_RESERVED_ID).is_err()); | ||
| assert!(UserId::new(SYSTEM_RESERVED_ID).is_err()); |
serrrfirat
left a comment
There was a problem hiding this comment.
Code Review Summary — #4584
Verdict: REQUEST_CHANGES — 2 High findings (1 security, 2 tests missing critical coverage)
| # | Sev | Category | File | Finding |
|---|---|---|---|---|
| 1 | 🔴 High | Security | ids.rs:194 |
@deserialize_with_sentinel admits sentinel from untrusted JSON |
| 2 | 🟠 Medium | Security | ids.rs:179 |
system_sentinel() is pub — any crate can mint system-authority IDs |
| 3 | 🔴 High | Tests | scope.rs:103 |
TurnScope::to_resource_scope() sentinel fallback never tested |
| 4 | 🔴 High | Tests | contract.rs:33 |
ThreadScope::to_resource_scope() sentinel fallback never tested |
| 5 | 🟡 Medium | Tests | budget_accountant.rs:397 |
GovernorBackedAccountant no-actor branch never exercised |
| 6 | 🟡 Medium | Tests | filesystem_service.rs:510 (not in diff) |
No integration test for ownerless ThreadScope through full call site |
| 7 | ⚪ Low | Performance | ids.rs:194 |
Sentinel branch discards owned String, double-allocates |
| 8 | ⚪ Low | Tests | host_api_contract.rs:221 |
Stale comment contradicts new system_sentinel() API |
| 9 | ⚪ Low | Conventions | ids.rs:121 (not in diff) |
from_trusted doc still says "sentinel values" — contradicts system_sentinel guidance |
Reviewers: security ✅ bugs ✅ performance ✅ tests ✅ conventions ✅
Finding #6 (Medium — Tests — not in diff)
crates/ironclaw_threads/src/filesystem_service.rs — No integration test drives FilesystemSessionThreadService with ownerless ThreadScope through the full call site. .claude/rules/testing.md requires tests through the caller (not just the helper) when a transform gates a side effect and there is at least one wrapper. ThreadScope::to_resource_scope() is called at 20+ sites in filesystem_service.rs; a scope with owner_user_id=None routes all those writes to the sentinel path with no end-to-end coverage.
Fix: add tests::filesystem_thread_service_stores_and_reads_ownerless_scope covering ensure_thread + load_context_window with owner_user_id=None.
Finding #9 (Low — Conventions — not in diff)
crates/ironclaw_host_api/src/ids.rs:121 — The from_trusted doc-comment says "Reserved for sentinel values that intentionally contain bytes the validator rejects". The @sentinel_constructor arm added in this same PR explicitly says callers must use system_sentinel() instead. The stale doc actively misdirects readers.
Fix: update from_trusted doc to say: "Construct without validation. For non-sentinel trusted string rehydration (e.g. DB row values). To mint the system sentinel, use system_sentinel() instead."
| // Admit the system sentinel; `Self::new` rejects its | ||
| // control bytes, so JSON round-trip needs an explicit bypass. | ||
| if value == crate::SYSTEM_RESERVED_ID { | ||
| return Ok(Self::system_sentinel()); |
There was a problem hiding this comment.
🔴 High — Security — #1: @deserialize_with_sentinel admits sentinel from any untrusted JSON source
Before this diff, TenantId and UserId used @deserialize_strict which delegated to Self::new() → validate_scope_id() → rejects \x1f control bytes. No caller-supplied JSON could ever produce a sentinel-valued ID.
The new @deserialize_with_sentinel arm fires before validation with zero trust context: any Deserializer — including HTTP request bodies, queue messages, WASM host-call payloads, inter-process pipes — that supplies "\x1fSYSTEM\x1f" will successfully deserialize into a TenantId/UserId bearing the system sentinel. resource.rs still documents "no user-supplied identifier can ever collide", but that invariant is now broken.
All structs containing TenantId/UserId (ResourceScope, etc.) inherit this widened attack surface. Any future HTTP/RPC endpoint adding one of these types as a request-body field silently becomes an authority-elevation vector.
Fix: Do not admit the sentinel through the public Deserialize impl. Define a separate serde module used only in explicitly trusted deserialization sites (DB row reads, trusted inter-process messages):
// Separate trusted serde helper, used only with #[serde(with = "...")]
pub mod trusted_serde {
pub fn deserialize<'de, D: serde::Deserializer<'de>>(d: D) -> Result<TenantId, D::Error> {
let v = String::deserialize(d)?;
if v == crate::SYSTEM_RESERVED_ID { return Ok(TenantId::system_sentinel()); }
TenantId::new(v).map_err(serde::de::Error::custom)
}
}
// Default Deserialize stays strict:
impl<'de> Deserialize<'de> for TenantId {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let v = String::deserialize(d)?;
Self::new(v).map_err(serde::de::Error::custom)
}
}| /// `ResourceScope::is_system` returns `true` only when BOTH the | ||
| /// tenant and user ids of a scope come from this constructor; | ||
| /// no authority decision may key on a single field. | ||
| pub fn system_sentinel() -> Self { |
There was a problem hiding this comment.
🟠 Medium — Security — #2: system_sentinel() is pub — any crate can mint system-authority IDs without audit
system_sentinel() is pub on pub types TenantId/UserId. Every downstream crate — product adapters, integration glue, WASM shims — can call it to produce an ID that passes ResourceScope::is_system(). from_trusted() was already pub but anonymous; system_sentinel() is semantically named, prominently doc-commented as the "blessed path", and therefore far more discoverable. A developer writing a new service could call it to produce a "system" scope for what they believe is a privileged operation, bypassing per-tenant isolation without any capability check or audit event. Documentation-as-security is not an access-control mechanism.
Fix: Restrict to pub(crate), or if cross-crate use is genuinely needed, gate on a zero-sized capability token so the type system enforces the restriction:
// Option A (simplest):
pub(crate) fn system_sentinel() -> Self { ... }
// Option B (cross-crate with type-system enforcement):
pub fn system_sentinel(_: &crate::SystemSentinelAuthority) -> Self { ... }| scope.user_id = self | ||
| .explicit_owner_user_id() | ||
| .cloned() | ||
| .unwrap_or_else(UserId::system_sentinel); |
There was a problem hiding this comment.
🔴 High — Tests — #3: TurnScope::to_resource_scope() sentinel fallback path never tested
When explicit_owner_user_id() returns None, user_id falls back to UserId::system_sentinel(). No test asserts this branch executes or that the resulting ResourceScope carries user_id == SYSTEM_RESERVED_ID with is_system() == false (partial sentinel). Wrong user attribution silently routes billing and storage under the system slot.
Fix:
// crates/ironclaw_turns/tests/ (or inline)
#[test]
fn turn_scope_to_resource_scope_uses_sentinel_when_no_explicit_owner() {
let scope = TurnScope::new(
TenantId::new("tenant-abc").unwrap(), None, None,
ThreadId::new("thread-abc").unwrap(),
);
let rs = scope.to_resource_scope();
assert_eq!(rs.user_id.as_str(), SYSTEM_RESERVED_ID);
assert!(!rs.is_system(), "partial sentinel must not classify as system");
}| }), | ||
| user_id: self | ||
| .owner_user_id | ||
| .clone() |
There was a problem hiding this comment.
🔴 High — Tests — #4: ThreadScope::to_resource_scope() sentinel fallback (owner_user_id=None) never tested
ThreadScope::to_resource_scope() uses UserId::system_sentinel() when owner_user_id is None. All test helpers set owner_user_id: Some(...) — no test constructs a ThreadScope with owner_user_id: None and asserts the resulting ResourceScope has user_id == SYSTEM_RESERVED_ID and is_system() == false. Infrastructure/ownerless threads silently land in the sentinel user slot without coverage.
Fix:
#[test]
fn thread_scope_to_resource_scope_uses_sentinel_when_owner_absent() {
let scope = ThreadScope {
tenant_id: TenantId::new("tenant-x").unwrap(),
agent_id: AgentId::new("agent-x").unwrap(),
owner_user_id: None,
project_id: None, mission_id: None,
};
let rs = scope.to_resource_scope();
assert_eq!(rs.user_id.as_str(), SYSTEM_RESERVED_ID);
assert!(!rs.is_system());
}| @@ -397,7 +396,7 @@ impl GovernorBackedAccountant { | |||
| .actor | |||
| .as_ref() | |||
There was a problem hiding this comment.
🟡 Medium — Tests — #5: GovernorBackedAccountant no-actor branch never exercised
resource_scope() uses .unwrap_or_else(UserId::system_sentinel) when LoopRunContext::actor is None. Every test helper always sets an actor. No test verifies that pre_model_call with actor=None routes reservation to the sentinel user account rather than panicking or using a wrong scope.
Fix: tests::pre_model_call_uses_sentinel_user_when_actor_is_absent covering LoopRunContext without actor
| let value = String::deserialize(deserializer)?; | ||
| // Admit the system sentinel; `Self::new` rejects its | ||
| // control bytes, so JSON round-trip needs an explicit bypass. | ||
| if value == crate::SYSTEM_RESERVED_ID { |
There was a problem hiding this comment.
⚪ Low — Performance — #7: Sentinel branch discards owned String, double-allocates
Pre-PR: return Ok(Self::from_trusted(value)) reused the String already allocated by String::deserialize. Post-PR: return Ok(Self::system_sentinel()) calls SYSTEM_RESERVED_ID.to_string() for a new allocation, then drops value. Every sentinel deserialization hit now performs 2 heap allocations where 1 sufficed. Neither system_sentinel nor from_trusted carries #[inline], so the compiler is not guaranteed to optimize this away.
Fix: In the @deserialize_with_sentinel arm only, reuse the owned string:
if value == crate::SYSTEM_RESERVED_ID {
return Ok(Self::from_trusted(value)); // reuse owned String
}This preserves the auditability goal (the equality check above is the sentinel gate) while avoiding the extra allocation.
| assert_eq!(decoded.tenant_id.as_str(), SYSTEM_RESERVED_ID); | ||
| assert_eq!(decoded.user_id.as_str(), SYSTEM_RESERVED_ID); | ||
|
|
||
| // Sentinel stays unreachable through ordinary construction; only |
There was a problem hiding this comment.
⚪ Low — Tests — #8: Stale comment contradicts new system_sentinel() API
This comment says "only from_trusted may produce it" but the PR adds system_sentinel() as the blessed cross-crate constructor (and the adjacent system_sentinel_constructor_produces_the_reserved_value test pins exactly this). The comment now contradicts the new API contract.
Fix:
// Sentinel stays unreachable through ordinary construction; the blessed
// minting paths are `TenantId::system_sentinel()` / `UserId::system_sentinel()`
// (and the lower-level `from_trusted` escape hatch they wrap),
// so user input can never collide.…nary/ironclaw into security/pr-4575-tests-and-docs
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughSummary by CodeRabbit
WalkthroughIntroduces a dedicated ChangesSystem Sentinel Constructor Rollout
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/ironclaw_host_api/src/ids.rs (1)
230-236: ⚡ Quick winClarify the macro comment: sentinel is NOT admitted during bare ID JSON deserialization.
The comment says "admitting [
crate::SYSTEM_RESERVED_ID] during JSON deserialization" but the@deserialize_strictarm is still used for these types, so the bareTenantId/UserIdDeserializeimpl rejects the sentinel. Sentinel admission only happens viaResourceScope's field-leveldeserialize_withhelpers. Consider rewording to avoid confusion:-// SECURITY-CRITICAL: `accepts_system_sentinel` opts these id types into -// admitting [`crate::SYSTEM_RESERVED_ID`] during JSON deserialization and -// exposes the `system_sentinel()` constructor — the only blessed path that -// can mint a sentinel-valued id. Every other id kind stays strict and -// rejects the sentinel on the wire. Adding this flag to any new id type -// requires security review: any HTTP/RPC endpoint that deserializes such an -// id from an untrusted request body becomes an authority-elevation vector. +// SECURITY-CRITICAL: `accepts_system_sentinel` exposes the `system_sentinel()` +// constructor — the only blessed path for minting a sentinel-valued id. The +// bare id's `Deserialize` impl stays STRICT (rejects the sentinel on the wire); +// sentinel admission during deserialization is scoped to trusted-persistence +// shapes like `ResourceScope`'s field-level `deserialize_with` helpers. +// Adding this flag to any new id type requires security review.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ironclaw_host_api/src/ids.rs` around lines 230 - 236, The comment block for the `accepts_system_sentinel` flag at lines 230-236 incorrectly implies that the sentinel is admitted during bare JSON deserialization of id types like TenantId and UserId. Clarify the comment to accurately reflect that the `@deserialize_strict` arm still rejects the sentinel during bare deserialization, and that sentinel admission only occurs via ResourceScope's field-level `deserialize_with` helpers. The flag should be described as exposing the `system_sentinel()` constructor (the blessed path to mint sentinel-valued ids) while maintaining strict deserialization at the bare id type level.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/ironclaw_host_api/src/ids.rs`:
- Around line 230-236: The comment block for the `accepts_system_sentinel` flag
at lines 230-236 incorrectly implies that the sentinel is admitted during bare
JSON deserialization of id types like TenantId and UserId. Clarify the comment
to accurately reflect that the `@deserialize_strict` arm still rejects the
sentinel during bare deserialization, and that sentinel admission only occurs
via ResourceScope's field-level `deserialize_with` helpers. The flag should be
described as exposing the `system_sentinel()` constructor (the blessed path to
mint sentinel-valued ids) while maintaining strict deserialization at the bare
id type level.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 28a501f8-4748-4299-a22a-e6277931d802
📒 Files selected for processing (6)
crates/ironclaw_host_api/src/ids.rscrates/ironclaw_host_api/src/resource.rscrates/ironclaw_host_api/tests/host_api_contract.rscrates/ironclaw_loop_support/src/budget_accountant.rscrates/ironclaw_threads/src/contract.rscrates/ironclaw_turns/src/scope.rs
| // SECURITY-CRITICAL: `accepts_system_sentinel` opts these id types into | ||
| // admitting [`crate::SYSTEM_RESERVED_ID`] during JSON deserialization and | ||
| // exposes the `system_sentinel()` constructor — the only blessed path that | ||
| // can mint a sentinel-valued id. Every other id kind stays strict and | ||
| // rejects the sentinel on the wire. Adding this flag to any new id type | ||
| // requires security review: any HTTP/RPC endpoint that deserializes such an | ||
| // id from an untrusted request body becomes an authority-elevation vector. |
| /// SECURITY-CRITICAL: this is the blessed path for minting a | ||
| /// sentinel-valued id. It exists so every authority-elevating | ||
| /// constructor is `git grep`-able as `system_sentinel`, rather | ||
| /// than hidden behind the more general `from_trusted` escape | ||
| /// hatch. New code that needs the sentinel must call this | ||
| /// method — never `from_trusted(SYSTEM_RESERVED_ID.to_string())`. |
| // SECURITY GUARD (PR review finding #1): NO bare id type — not even the | ||
| // sentinel-aware TenantId/UserId — admits the sentinel during its own | ||
| // `Deserialize`. Sentinel admission lives exclusively on `ResourceScope`'s |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/ironclaw_host_api/src/resource.rs`:
- Around line 68-69: Replace the `from_trusted(raw)` calls with
`system_sentinel()` in the deserialization helpers to maintain the auditability
contract. When deserializing TenantId and checking if raw equals
SYSTEM_RESERVED_ID, use the sentinel-aware `system_sentinel()` constructor
instead of `from_trusted(raw)` to ensure these authority-elevating constructor
calls are discoverable via git grep and align with the behavior of
ResourceScope::system(). This fix applies to multiple locations in the
deserialization logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 408a85c9-28eb-4fd9-a12d-1affc7e34309
📒 Files selected for processing (2)
crates/ironclaw_host_api/src/resource.rscrates/ironclaw_host_api/tests/host_api_contract.rs
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/ironclaw_host_api/src/resource.rs`:
- Around line 68-69: Replace the `from_trusted(raw)` calls with
`system_sentinel()` in the deserialization helpers to maintain the auditability
contract. When deserializing TenantId and checking if raw equals
SYSTEM_RESERVED_ID, use the sentinel-aware `system_sentinel()` constructor
instead of `from_trusted(raw)` to ensure these authority-elevating constructor
calls are discoverable via git grep and align with the behavior of
ResourceScope::system(). This fix applies to multiple locations in the
deserialization logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 408a85c9-28eb-4fd9-a12d-1affc7e34309
📒 Files selected for processing (2)
crates/ironclaw_host_api/src/resource.rscrates/ironclaw_host_api/tests/host_api_contract.rs
🛑 Comments failed to post (1)
crates/ironclaw_host_api/src/resource.rs (1)
68-69:
⚠️ Potential issue | 🟠 Major | ⚡ Quick winDeserialize helpers bypass
system_sentinel(), breaking the stated auditability contract.PR objective: "make every authority-elevating constructor discoverable via
git grepforsystem_sentinel" and "The same constructor is reused in the sentinel-aware Deserialize implementation".The code calls
from_trusted(raw)instead ofsystem_sentinel(). This meansgit grep system_sentinelwill miss these two call sites, and the JSON round-trip path diverges fromResourceScope::system().Proposed fix
fn deserialize_system_aware_tenant_id<'de, D>(deserializer: D) -> Result<TenantId, D::Error> where D: serde::Deserializer<'de>, { let raw = String::deserialize(deserializer)?; if raw == SYSTEM_RESERVED_ID { - Ok(TenantId::from_trusted(raw)) + Ok(TenantId::system_sentinel()) } else { TenantId::new(raw).map_err(serde::de::Error::custom) } } fn deserialize_system_aware_user_id<'de, D>(deserializer: D) -> Result<UserId, D::Error> where D: serde::Deserializer<'de>, { let raw = String::deserialize(deserializer)?; if raw == SYSTEM_RESERVED_ID { - Ok(UserId::from_trusted(raw)) + Ok(UserId::system_sentinel()) } else { UserId::new(raw).map_err(serde::de::Error::custom) } }Also applies to: 80-81
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/ironclaw_host_api/src/resource.rs` around lines 68 - 69, Replace the `from_trusted(raw)` calls with `system_sentinel()` in the deserialization helpers to maintain the auditability contract. When deserializing TenantId and checking if raw equals SYSTEM_RESERVED_ID, use the sentinel-aware `system_sentinel()` constructor instead of `from_trusted(raw)` to ensure these authority-elevating constructor calls are discoverable via git grep and align with the behavior of ResourceScope::system(). This fix applies to multiple locations in the deserialization logic.
Summary
TenantId::system_sentinel()/UserId::system_sentinel()constructor as the only sanctionedpath to mint a
SYSTEM_RESERVED_ID-valued id, and route the four legitimate call sites (ResourceScope::system,GovernorBackedAccountant,ThreadScope::to_resource_scope,TurnScope::to_resource_scope) through it insteadof the general
from_trustedescape hatch. The point is auditability: every authority-elevating constructor isnow
git grep-able assystem_sentinel.Deserializeimpl so the JSON round-trip path and thein-process path mint the sentinel through identical code.
accepts_system_sentinelmacro arm: opting a new id kind in is a